TypeScript:获取泛型构造函数的类型

Mat*_*ner 5 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)
}
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)

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

jca*_*alz 5

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

Playground 链接到代码