ajm*_*mnz 5 arrays typescript typescript-generics
我正在尝试编写一个函数,该函数将数组中特定对象的两个键作为参数。密钥如下:
对象数组如下
const obj = [
{
name: "number",
func: (foo: number) => foo,
},
{
name: "string",
func: (foo: string) => foo,
},
{
name: "array",
func: (foo: string[]) => foo,
},
];
Run Code Online (Sandbox Code Playgroud)
这是我的方法
type SingleObject = typeof obj[number];
type Name<T extends SingleObject> = T["name"];
type FuncParams<T extends SingleObject> = Parameters<T["func"]>;
const myFunc = async <T extends SingleObject>(
name: Name<T>,
params: FuncParams<T>
) => {
// Do something
};
Run Code Online (Sandbox Code Playgroud)
我认为当调用myFunc传递"array"(例如)作为第一个参数时,它会识别有问题的对象并正确分配另一个键的参数(在这种情况下应该是Parameters<(foo: string[] => foo)>)
显然情况并非如此,因为我可以毫无问题地执行以下操作
myFunc("number", ["fo"]); // Should only allow a number as the second parameter
myFunc("string", [34]); // Should only allow a string as the second parameter
myFunc("array", [34]); // Should only allow an array of strings as the second parameter
Run Code Online (Sandbox Code Playgroud)
参考
默认情况下,编译器会推断出一个字符串值属性的类型string,而不是一个字符串文本类型一样"number"。
/* const obj: ({
name: string; // oops
func: (foo: number) => number;
} | {
name: string; // oops
func: (foo: string) => string;
} | {
name: string; // oops
func: (foo: string[]) => string[];
})[]
*/
Run Code Online (Sandbox Code Playgroud)
为了让你的计划可能是工作,你需要说服你关心的实际文本值编译name你的元素属性obj。最简单的方法是在的初始化程序上使用const断言obj:
const obj = [
{
name: "number",
func: (foo: number) => foo,
},
{
name: "string",
func: (foo: string) => foo,
},
{
name: "array",
func: (foo: string[]) => foo,
},
] as const; // <-- const assertion
Run Code Online (Sandbox Code Playgroud)
现在的类型obj是:
/* const obj: readonly [{
readonly name: "number";
readonly func: (foo: number) => number;
}, {
readonly name: "string";
readonly func: (foo: string) => string;
}, {
readonly name: "array";
readonly func: (foo: string[]) => string[];
}] */
Run Code Online (Sandbox Code Playgroud)
其中name字段是需要的类型"number","string"和"array"。
接下来,让编译器推断泛型类型参数的类型的最佳方法是为编译器提供该类型的值以从中推断它。也就是说,T从类型值进行推断T很简单,但从T类型值进行推断T["name"]则不然。
所以我的建议是这样的:
/* type NameToParamsMap = {
number: [foo: number];
string: [foo: string];
array: [foo: string[]];
} */
const myFunc = async <K extends keyof NameToParamsMap>(
name: K,
params: NameToParamsMap[K]
) => {
// Do something
};
Run Code Online (Sandbox Code Playgroud)
该类型NameToParamsMap是一个辅助对象类型,其键是name来自obj元素的字段,其属性是来自相关func字段的参数列表。然后myFunc可以K从name传入的参数值推断出泛型类型参数。一旦K正确推断,编译器可以检查params参数以确保它匹配。
你当然不想NameToParamsMap手写;您可以让编译器从typeof obj以下位置计算它:
type NameToParamsMap = {
[T in typeof obj[number] as T["name"]]: Parameters<T["func"]> }
Run Code Online (Sandbox Code Playgroud)
这里我使用密钥重新映射在每个元件进行迭代T的typeof obj[number],以及使用T["name"]和T["func"]以产生所述对象的键和值。
让我们确保它有效:
myFunc("number", [1]); // okay
myFunc("number", ["oops"]) // error
// -------------> ~~~~~~
// Type 'string' is not assignable to type 'number'
myFunc("string", [34]); // error
myFunc("array", [34]); // error
Run Code Online (Sandbox Code Playgroud)
看起来挺好的!