鏈接到 TS Playground
我有一個名為genWizardDefaultState的函式,用于為表單生成默認狀態。它是遞回的,因此它可以支持嵌套表單。我正在嘗試創建一個遞回泛型型別,以便該函式實際上回傳一個有用的型別化輸出。
該函式本身接受一個類似 JSON 的物件,其值只能是Args,并回傳一個具有相同結構的物件,其值為Fields。引數定義如下:
interface TextArg {
fieldType: "text";
}
interface NumberArg {
fieldType: "number";
}
interface SelectArg {
fieldType: "select";
}
type Arg = TextArg | NumberArg | SelectArg;
欄位定義如下:
interface TextField {
value: string;
errors: string[];
}
interface NumberField {
value: number;
errors: string[];
}
interface SelectField {
value: any;
label: string;
errors: string[];
}
更通用的Field型別是通用的:它接受一個 Arg 并根據 fieldType 屬性回傳一個特定的 Field:
interface ArgToFieldMap {
text: TextField;
number: NumberField;
select: SelectField;
boolean: BooleanField;
date: DateField;
}
type Field<T extends Arg> = ArgToFieldMap[T["fieldType"]];
這適用于平面物件輸入(只要我在輸入型別上使用 const 斷言,以便打字稿實際上將 fieldType 推斷為字串文字而不僅僅是字串):
type FormFields<T extends Record<keyof T, Arg>> = {
[Key in keyof T]: Field<T[Key]>;
};
例如,這個片段確實回傳了正確的型別!
const exampleArgsObject = {
name: { fieldType: "text" },
age: { fieldType: "number" },
favoriteTowel: { fieldType: "select" },
} as const;
type formStateType = FormFields<typeof exampleArgsObject>;
但是在使這種型別遞回時,我碰壁了。我想我需要:
- 告訴 FormFields 其輸入型別的屬性是
ArgorArray<FormField>。 - 使用條件型別作為映射的輸出屬性,以區分
Arg和Array<FormField>輸入屬性
到目前為止,這是我想出的:
type FormFieldsRecursive<T extends Record<keyof T, Arg | Array<FormFieldsRecursive<T>>>> = {
[Key in keyof T]: T[Key] extends Arg ? Field<T[Key]> : 'idk what goes here'
};
知道我應該回傳什么而不是“idk what goes here”嗎?我知道它應該是某種回傳陣列的泛型型別,但我真的很困惑。我開始懷疑這是否可能!
uj5u.com熱心網友回復:
首先,讓我們放棄form的通用約束。FormFields<T>T extends Record<keyof T, Arg | Array<FormFieldsRecursive<T>>>
該版本不起作用,因為FormFieldsRecursive<T>它是您的型別轉換的輸出。如果你我們真的想給輸入一個名字,它可能看起來像這樣:
type ArgsObj = { [k: string]: Arg | ReadonlyArray<ArgsObj> };
這里我說 anArgsObj有一個字串索引簽名,所以我們不關心它的鍵。(這實際上將無法匹配沒有顯式索引簽名的介面,如microsoft/TypeScript#15300中所述)。然后屬性是元素Arg或元素陣列ArgsObj。好吧,一個只讀陣列,因為您正在使用const斷言,而這些斷言會產生只讀陣列。常規陣列是只讀陣列的子型別,因此這不會限制任何內容。
因此,如果我們愿意,我們可以定義type FormFields<T extends ArgsObj> = ...這樣你就不能傳入 "bad" T。
但我不會打擾。它只會讓事情變得更復雜。如果您在某處確實有需要確保型別屬于該ArgsObj型別的函式,則可以將約束放在那里。
好的,那么我們怎樣才能FormFields<T>以遞回方式作業呢?我發現以下方法將操作分為兩種最簡單的方法。一種型別,FormFields<T>,映射整個物件型別,而FormField<T>將映射該物件型別的單個屬性。它的核心是這樣的:
type FormFields<T> = {
[K in keyof T]: FormField<T[K]>
}
type FormField<T> =
T extends Arg ? Field<T> : { [I in keyof T]: FormFields<T[I]> };
FormFields<T>一個簡單的映射型別也是如此,它映射Twith的每個屬性FormField<>。AndFormField<T>是一個條件型別,用于檢查屬性是否為Arg. 如果是這樣,它Field<T>就像以前一樣。如果不是,那么我們知道它必須是一個包含適合的型別的陣列型別FormFields<>,在這種情況下,我們使用對映射陣列/元組到具有映射型別T的陣列/元組的支持來將at 類數字索引的每個元素I轉換為FormFields<T[I]>。
我們可以檢查這是否有效:
const exampleArgsObjectRecursive = {
name: { fieldType: "text" },
age: { fieldType: "number" },
favTowel: { fieldType: "select" },
likesKanye: { fieldType: "boolean" },
subForms: [
{
shoeColor: { fieldType: "text" },
},
{
shoeColor: { fieldType: "text" },
},
]
} as const;
type GeneratedRecursiveExampleFieldsType = FormFields<typeof exampleArgsObjectRecursive>;
/* type GeneratedRecursiveExampleFieldsType = {
readonly name: TextField;
readonly age: NumberField;
readonly favTowel: SelectField;
readonly likesKanye: BooleanField;
readonly subForms: readonly [FormFields<{
readonly shoeColor: {
readonly fieldType: "text";
};
}>, FormFields<{
readonly shoeColor: {
readonly fieldType: "text";
};
}>];
} */
所以看起來不錯,除了型別顯示以FormFields<>. 如果我們希望編譯器實際上完全擴展型別,我們可以使用另一個 SO 問題中顯示的方法,即采用映射型別并執行額外的infer映射型別。一般形式是我們采用一個型別XXX并將其轉換為XXX extends infer U ? { [K in keyof U]: U[K] } : never. 像這樣:
type FormFields<T> = {
[K in keyof T]: FormField<T[K]>
} extends infer U ? { [K in keyof U]: U[K] } : never;
現在我們得到
type GeneratedRecursiveExampleFieldsType = FormFields<typeof exampleArgsObjectRecursive>;
/* type GeneratedRecursiveExampleFieldsType = {
readonly name: TextField;
readonly age: NumberField;
readonly favTowel: SelectField;
readonly likesKanye: BooleanField;
readonly subForms: readonly [{
readonly shoeColor: TextField;
}, {
readonly shoeColor: TextField;
}];
} */
如預期的!
Playground 代碼鏈接
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/484768.html
上一篇:請確保指定位置存在聲音檔案
下一篇:遞回方法回傳后執行代碼
