我不知道如何正确地问这个问题,因为我不明白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
等(我认为可能有一个更好的选择,而不是铸造到unknown
或any
):
...
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
调用上,您将注意到由您提供的参数推断出泛型类型。这里是通用约束的文档。