Class 方法 return 基于参数类型的类型
Class method return type based on type of parameter
我有一个接受任何东西作为参数的函数,但是如果它接收到一个 function
(带原型)它应该 return 它的一个实例 函数(或 class,因为它们是函数)。
如果不是,它可能看起来在 Map 和 return 中分配给该键的任何东西。
超级简化的代码看起来像 (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
参数创建特定类型?定义重载?怎么样?
提前致谢
我认为您对 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
对应于 zero-arg 构造函数,则 return 类型将是该构造函数的实例类型。否则 return 类型是 unknown
。请注意,编译器无法验证实现是否对应于该调用签名(请参阅 microsoft/TypeScript#33912 for more info), so I'm using a single-call signature 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()
带有零个或一个附加参数(一个参数元组type [value?: I]
其中 I
是 class 实例类型),但如果 key
不是构造函数,则必须使用附加参数调用 set()
( [value: unknown]
.
类型的参数元组
然后如果有人试图用构造函数传递一些奇怪的东西,你会得到编译器错误:
service.set(Animal, 1234); // error!
service.set(Animal); // okay
service.set('bob', new Animal('Bob')) // okay
最后,我真的不确定如何跟踪为 non-constructor 键输入的值的类型。编译器将它们视为 unknown
,这意味着如果您设置并获取它,您可能会丢失信息:
service.set("string", "hello");
console.log(service.get("string").toUpperCase()); // error at compile time
// but HELLO at runtime
你要么需要使用 type assertions or a type guard 来处理:
const str = service.get("string") as string; // I'm telling the compiler it's a string
console.log(str.toUpperCase()); // works now
const str2 = service.get("string");
console.log(
typeof str2 === "string" ? str2.toUpperCase() : "NOT A STRING"
); // I'm testing
可能还有其他方法可以使用 assertion functions or a 来跟踪类型,但这似乎超出了问题的范围。
我有一个接受任何东西作为参数的函数,但是如果它接收到一个 function
(带原型)它应该 return 它的一个实例 函数(或 class,因为它们是函数)。
如果不是,它可能看起来在 Map 和 return 中分配给该键的任何东西。
超级简化的代码看起来像 (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
参数创建特定类型?定义重载?怎么样?
提前致谢
我认为您对 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
对应于 zero-arg 构造函数,则 return 类型将是该构造函数的实例类型。否则 return 类型是 unknown
。请注意,编译器无法验证实现是否对应于该调用签名(请参阅 microsoft/TypeScript#33912 for more info), so I'm using a single-call signature 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()
带有零个或一个附加参数(一个参数元组type [value?: I]
其中 I
是 class 实例类型),但如果 key
不是构造函数,则必须使用附加参数调用 set()
( [value: unknown]
.
然后如果有人试图用构造函数传递一些奇怪的东西,你会得到编译器错误:
service.set(Animal, 1234); // error!
service.set(Animal); // okay
service.set('bob', new Animal('Bob')) // okay
最后,我真的不确定如何跟踪为 non-constructor 键输入的值的类型。编译器将它们视为 unknown
,这意味着如果您设置并获取它,您可能会丢失信息:
service.set("string", "hello");
console.log(service.get("string").toUpperCase()); // error at compile time
// but HELLO at runtime
你要么需要使用 type assertions or a type guard 来处理:
const str = service.get("string") as string; // I'm telling the compiler it's a string
console.log(str.toUpperCase()); // works now
const str2 = service.get("string");
console.log(
typeof str2 === "string" ? str2.toUpperCase() : "NOT A STRING"
); // I'm testing
可能还有其他方法可以使用 assertion functions or a