在 TypeScript 中,我定义了一个名为的辅助函数create,它接受一个构造函数和构造函数的类型参数来创建一个类的新实例:
function create<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(
ctor: Ctor,
...args: ConstructorParameters<Ctor>
): R {
return new ctor(...args)
}
Run Code Online (Sandbox Code Playgroud)
这适用于像这样的简单类:
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');
Run Code Online (Sandbox Code Playgroud)
但是,如果我有一个泛型类,则无法让 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' })
Run Code Online (Sandbox Code Playgroud)
根本原因是create第二种情况下的签名采用一个unknown参数:
是否可以在调用中显式写出泛型类构造函数create的类型而不内联类型本身,同时仍然保留create?
我的第一个想法是:
create<typeof Foo<number>, Foo<number>>(Foo, { not: 'a number' })
Run Code Online (Sandbox Code Playgroud)
但那是无效代码。
到目前为止,我发现的最佳解决方法是使用临时类来显式捕获构造函数类型:
class Temp extends Foo<number> { }
// This correctly generates an error
create<typeof Temp, Foo<number>>(Foo, { not: 'a number' });
Run Code Online (Sandbox Code Playgroud)
这可以以更优雅的方式完成吗?
该create()函数A在构造函数的参数列表中应该是泛型的,以及构造的实例类型R。这利用了TypeScript 3.4 中引入的高阶类型推断。
function create<A extends any[], R>(
ctor: new (...args: A) => R,
...args: A
): R {
return new ctor(...args)
}
Run Code Online (Sandbox Code Playgroud)
从那里开始,问题是如何指定类型,以便Foo<number>在您的参数不正确时请求 a并得到某种错误。一种方法是手动指定通用参数:
// Specify generics
const fooSpecifiedGood = create<[number], Foo<number>>(Foo, 123); // okay
const fooSpecifiedBad = create<[number], Foo<number>>(Foo, { not: 'a number' }); // error
Run Code Online (Sandbox Code Playgroud)
那个有效。如果,正如您所提到的,您的构造函数参数列表太长或太复杂而无法写出,那么我建议您只注释保存返回实例的变量的类型,如下所示:
// 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
Run Code Online (Sandbox Code Playgroud)
现在,您可能更希望能够调用create<Foo<number>>(Foo, 123)手动将R参数指定为 的位置Foo<number>,但让编译器推断该A参数。不幸的是,TypeScript 目前不支持这种部分类型参数推断。对于任何给定的泛型函数调用,您可以手动指定所有类型参数,也可以让编译器推断所有类型参数,基本上就是这样(类型参数默认值使故事有点复杂,但它仍然没有给你这种能力)。一种解决方法是使用currying将单个<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
Run Code Online (Sandbox Code Playgroud)
所以这些是我看到的主要选项。我个人会使用这种fooAnnotated方法,因为它类似于const oops: Foo<number> = new Foo({not: 'a number'}). 错误不在于调用Foo构造函数;假设结果是Foo<number>. 无论如何,希望其中之一对您有所帮助。祝你好运!
| 归档时间: |
|
| 查看次数: |
818 次 |
| 最近记录: |