我有一个函数,可以接受任何东西作为参数,但是如果它接收到function
(带有原型),则它应该返回该函数(或类的实例),因为它们是函数)。
如果没有,它可能会在地图中看起来像分配给该键的任何东西,然后将其返回。
超级简化的代码看起来像(playground):
// Go check service.get() method
const isConstructable = (fn: any) => typeof fn === 'function' && 'prototype' in fn;
class Animal {
name: string | undefined
constructor(name?: string){
this.name = name;
}
}
const service = new (class Service {
map = new Map()
set(key: any, value: any = key) {
return this.map.set(key, value)
}
get<T>(key: { new (): T }): T {
const ToResolve = this.map.get(key);
if (isConstructable(ToResolve)) {
return new ToResolve();
}
return ToResolve;
}
})
service.set('bob', new Animal('Bob'))
service.set(Animal);
const genericAnimal = service.get(Animal)
const bob = service.get('bob') // Argument of type 'string' is not assignable to parameter of type 'new () => unknown'
console.log(genericAnimal instanceof Animal)
console.log(bob instanceof Animal)
我正在阅读Using Class Types in Generics并找到了这段代码
function create<T>(c: { new (): T }): T {
return new c();
}
它运作良好,但是我需要get(key)
不仅接受构造函数,而且接受Map
接受的任何原语,例如:
const jane = {} // object reference, since TS won't let me use symbols
service.set(jane, class Jane extends Animal {
constructor() {
super('Jane')
}
})
console.log(service.get(jane).name === 'Jane')
这样,我会遇到类似Argument of type 'string' is not assignable to parameter of type 'new () => unknown'
的编译错误
还尝试了:get<T>(key: T): T extends Function ? T : unknown
,虽然显然不起作用,但感觉非常接近:(
是否需要为key
参数创建特定类型?定义重载?怎么样?
预先感谢
答案 0 :(得分:2)
我认为您对get
的签名非常接近。如果要使用条件类型,可以这样写:
get<T>(key: T): T extends new () => infer I ? I : unknown;
get(key: any) {
const ToResolve = this.map.get(key);
if (isConstructable(ToResolve)) {
return new ToResolve();
}
return ToResolve;
}
在这种情况下,如果类型T
对应于零参数构造函数,则返回类型将是该构造函数的实例类型。否则,返回类型为unknown
。请注意,编译器无法验证实现是否与该调用签名相对应(有关更多信息,请参见microsoft/TypeScript#33912),因此我使用的是实现较为宽松的单调用签名overload。这样编译就可以了,但是需要注意的是,您有责任确保实现符合呼叫签名。
这对于您的第一组示例使用是理想的:
const genericAnimal = service.get(Animal) // Animal
const bob = service.get('bob') // unknown
但是当然,set()
方法的类型太松散,以至于不能保证它会被正确使用。
service.set(Animal, 1234); // no error? oops.
我建议更改set()
方法以反映get()
方法中发生的事情:
set<T>(
key: T,
...args: T extends new () => infer I ? [value?: I] :
[value: unknown]
): void;
set(key: any, value?: any) {
this.map.set(key, typeof value === "undefined" ? key : value);
}
该签名可能看起来很吓人,但基本上是说,如果key
是构造函数,则可以使用零或一个附加参数(类型为{的参数元组)调用set()
{1}},其中[value?: I]
是类实例类型,但是如果I
不是构造函数,则必须使用附加参数(类型为{的参数元组)调用key
{1}}。
然后,如果有人尝试通过构造函数传递一些奇怪的信息,则会出现编译器错误:
set()
最后,我真的不确定如何跟踪为非构造函数键输入的值的类型。编译器将其视为[value: unknown]
,这意味着如果您设置并获得它,则可能会丢失信息:
service.set(Animal, 1234); // error!
service.set(Animal); // okay
service.set('bob', new Animal('Bob')) // okay
您将需要使用type assertions或type guard来解决此问题:
unknown
可能还有其他方法可以使用assertion functions或fluent API来跟踪类型,但这似乎超出了问题的范围。