【问题标题】:TypeScript: get typeof generic constructorTypeScript:获取 typeof 泛型构造函数
【发布时间】:2019-12-18 01:30:07
【问题描述】:

在 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' });

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

【问题讨论】:

  • 您能改用this 吗?它使用higher order function inference in TS3.4+,但类型参数不同。
  • 如果它满足您的需求,我可以写出来作为答案。
  • 感谢您的浏览。我试图避免显式输入构造函数的参数类型。参数列表在实际代码中可能会变得很长,并且在多个位置显式键入它会使重构变得困难
  • 我不知道你为什么要在函数调用中指定泛型。如果您希望函数返回 Foo&lt;number&gt;,那么您可以对其进行注释,如果您没有传递正确的内容,则会看到错误,例如 this
  • 还有generic currying,但这可能不如我上一条评论中的注释有用。

标签: typescript


【解决方案1】:

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&lt;number&gt; 并在您的参数不正确时得到某种错误。一种方法是手动指定泛型参数:

// 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&lt;Foo&lt;number&gt;&gt;(Foo, 123),您手动将R 参数指定为Foo&lt;number&gt;,但让编译器推断A 参数。不幸的是,TypeScript 目前不支持这样的partial type parameter inference。对于任何给定的泛型函数调用,您可以手动指定所有类型参数,也可以让编译器推断所有类型参数,基本上就是这样(type parameter defaults 使故事复杂一点,但它仍然没有给你这种能力)。一种解决方法是使用currying 获取像&lt;A, R&gt;()=&gt;... 这样的单个函数并将其转换为像&lt;A&gt;()=&gt;&lt;R&gt;()=&gt;... 这样的高阶函数。将此技术应用于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&lt;number&gt; = new Foo({not: 'a number'})。错误不在于调用Foo 构造函数;假设结果是Foo&lt;number&gt;。无论如何,希望其中之一对您有所帮助。祝你好运!

Playground link to code

【讨论】:

    猜你喜欢
    • 2020-11-15
    • 2016-04-05
    • 1970-01-01
    • 2018-06-04
    • 1970-01-01
    • 2019-09-19
    • 2019-05-04
    • 2021-06-16
    • 1970-01-01
    相关资源
    最近更新 更多