在TypeScript中,我定义了一个名为create
的帮助程序函数,该函数接受构造函数和构造函数的类型化参数来创建类的新实例:
function create<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(
ctor: Ctor,
...args: ConstructorParameters<Ctor>
): R {
return new ctor(...args)
}
这适用于像这样的简单类:
class Bar {
constructor(param: number) { }
}
const bar1 = create<typeof Bar, Bar>(Bar, 1);
// Compile error: Argument of type '"string"' is not assignable to parameter of type 'number'.ts(2345)
const bar2 = create<typeof Bar, Bar>(Bar, 'string');
但是,如果我有通用类,则无法让TypeScript对create
执行正确的类型检查:
class Foo<T> {
constructor(param: T) { }
}
// Ok
const foo1 = create<typeof Foo, Foo<number>>(Foo, 1);
// This should be an error but is not
const foo2 = create<typeof Foo, Foo<number>>(Foo, { not: 'a number' })
根本原因是第二种情况下create
的签名采用了unknown
参数:
是否可以在对create
的调用中显式写出通用类构造函数的类型,而无需内联类型本身,而仍保留create
的签名?
我的第一个念头是:
create<typeof Foo<number>, Foo<number>>(Foo, { not: 'a number' })
但这是无效的代码。
到目前为止,我发现最好的解决方法是使用一个临时类来显式捕获构造函数类型:
class Temp extends Foo<number> { }
// This correctly generates an error
create<typeof Temp, Foo<number>>(Foo, { not: 'a number' });
这可以用更优雅的方式完成吗?
答案 0 :(得分:5)
create()
函数应该在构造函数的参数列表A
中以及构造的实例类型R
中是通用的。这利用了TypeScript 3.4中引入的higher order type inference。
function create<A extends any[], R>(
ctor: new (...args: A) => R,
...args: A
): R {
return new ctor(...args)
}
从那里开始,问题是如何指定类型,以便在参数不正确的情况下要求输入Foo<number>
并出现某种错误。一种方法是手动指定通用参数:
// Specify generics
const fooSpecifiedGood = create<[number], Foo<number>>(Foo, 123); // okay
const fooSpecifiedBad = create<[number], Foo<number>>(Foo, { not: 'a number' }); // error
那行得通。如您所述,如果构造函数参数列表太长或太复杂而无法写出,那么我建议您只注释一个变量类型,将返回的实例保存到该变量中,如下所示:
// Just annotate the variable you're writing to
const fooAnnotatedGood: Foo<number> = create(Foo, 123); // okay
const fooAnnotatedBad: Foo<number> = create(Foo, { not: 'a number' }); // error
现在,您可能更希望能够调用create<Foo<number>>(Foo, 123)
,在此处手动将R
参数指定为Foo<number>
,但是让编译器推断 { {1}}参数。不幸的是,TypeScript当前不支持此类partial type parameter inference。对于任何给定的通用函数调用,您既可以手动指定所有类型参数,也可以让编译器推断所有类型参数,基本上就是这样(type parameter defaults使故事复杂化了一点,但仍然没有给你这种能力)。一种解决方法是使用currying接受单个函数,如A
并将其转换为更高阶的函数,如<A, R>()=>...
。将这项技术应用于<A>()=><R>()=>...
可为您提供:
create()
这些是我看到的主要选项。我个人会使用// Or get a curried creator function that lets you use specify the instance type
// while allowing the compiler to infer the compiler args
const curriedCreate = <R>() =>
<A extends any[]>(ctor: new (...args: A) => R, ...args: A) => new ctor(...args);
const fooCurriedGood = curriedCreate<Foo<number>>()(Foo, 123); // okay
const fooCurriedBad = curriedCreate<Foo<number>>()(Foo, { not: 'a number' }); // error
方法,因为它类似于fooAnnotated
。错误不在于调用const oops: Foo<number> = new Foo({not: 'a number'})
构造函数;假设结果是Foo
。无论如何,希望其中之一对您有所帮助。祝你好运!