如何在TypeScript中正确键入和转换泛型?



我不知道如何正确地问这个问题,因为我不明白TypeScript中的泛型是如何工作的,但我知道如何解释它,例如,假设我有一个"function store"

const functionStore: Array <Function> = [];

我有一个函数添加函数到函数存储,这个函数允许接收一个对象,并指定将添加到通过参数传递的对象的函数的键的名称:

const addFunction = <Obj = { [index: string]: any }>(
obj: Obj,
...extractFunction: Array<keyof Obj>
) => {
for (const key of extractFunction) {
functionStore.push(obj[key]);
}
};
因此,如果我创建以下对象并将其传递给addFunction:
const obj = {
msg: 'Welcome!',
test() {
console.log('Hello!!');
},
doNoAdd() {
console.log('Do not add!');
},
otherTest() {
console.log('Other test!!');
}
};
addFunction(obj, 'test', 'otherTest');

这不起作用,因为'Obj[keyof Obj]'类型的参数不能赋值给'Function'类型的参数":

...
for (const key of extractFunction) {
functionStore.push(obj[key]); //<-- Error!!
}
...

如果我做一个铸造,它仍然会给出一个错误,因为是一个通用的,该属性可以是string,number,array等(我认为可能有一个更好的选择,而不是铸造到unknownany):

...
functionStore.push(obj[key] as Function); //<-- Conversion of type 'Obj[keyof Obj]' to type 
//     'Function' may be a mistake because neither
//     type sufficiently overlaps with the other. 
//     If this was intentional, convert the
//      expression to 'unknown' first.
...

我如何在键入中指定这些键,除了作为obj的键外,我还可以指定它引用对象obj中的函数?

我希望你能帮助我:)

为了使其更安全,您可以为Obj添加更多约束。

type Fn = (...args: any[]) => any
const functionStore: Array<Fn> = [];

type ExtractFn<T> = {
[Prop in keyof T]: T[Prop] extends Fn ? Prop : never
}[keyof T]
const addFunction = <Obj extends Record<string, any>>(
obj: Obj,
...extractFunction: Array<ExtractFn<Obj>>
) => {
for (const key of extractFunction) {
functionStore.push(obj[key]);
}
};
const obj = { name: 'John', getName: () => 'John' }
addFunction(obj, 'getName') // ok
addFunction(obj, 'name') // expected error

操场您可能已经注意到,不允许提供与功能不对应的键。

[Prop in keyof T]-参见映射类型。它遍历T类型中的每个属性。它就像javascript中的for..in循环。

T[Prop]-参见索引访问。type A={age:42}; type B = A['age'] // 42。工作方式与javascript相同。

T[Prop] extends Fn ? Prop : never-参见条件类型。这意味着如果T[Prop]Fn->returnProp,否则-never.

因此:

type ExtractFn<T> = {
[Prop in keyof T]: T[Prop] extends Fn ? Prop : never
}

遍历类型并检查property是否为函数。如果是函数,则将此函数替换为属性名,否则-替换为never

最后,使用[keyof T]:

type ExtractFn<T> = {
[Prop in keyof T]: T[Prop] extends Fn ? Prop : never
}[keyof T]

没有keyof T,ExtractFn返回:

ExtractFn<{msg:'string', doThing:()=>any}> // {msg:never, doThing:'doThing'}

所以我们需要得到所有值的并集。用keyof T很容易做到。实际上,它返回never | 'doThing'。因为never可赋值给任何类型,所以never | 'doThing'与'doThing'冲突。

的例子:

type Foo={age:42,name:string}['age'|'name'] // 42 | name

我希望你现在明白了。

问题在于泛型的定义方式,=运算符分配了一个默认类型,这可能永远不会发生,因为它总是由TS从类型Obj的参数推断出来。解决这个问题的方法是使用extend操作符,它将约束实参来扩展所提供的接口。

:

// this is pretty cool
const functionStore: Array <Function> = [];
// so it should extend the interface that you have defined
const addFunction = <Obj extends { [index: string]: any }>(
obj: Obj,
...extractFunction: Array<keyof Obj>
) => {
for (const key of extractFunction) {
functionStore.push(obj[key]);
}
};

应该可以了- playground


只是补充一下,如果您查看playground链接并将鼠标悬停在addFunction调用上,您将注意到由您提供的参数推断出泛型类型。这里是通用约束的文档。

相关内容

  • 没有找到相关文章

最新更新