TypeScript:获取typeof通用构造函数

时间:2019-12-18 01:30:07

标签: typescript

在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参数:

enter image description here

主要问题

是否可以在对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' });

这可以用更优雅的方式完成吗?

1 个答案:

答案 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。无论如何,希望其中之一对您有所帮助。祝你好运!

Playground link to code