avatar

fengkx's Blog

fengkx

Student & Coder

Guangzhou, China
Build with Hexo and Next.js

在 TypeScript 中获取 constructor 类型

TL;DR

export type ConstructorType<C extends abstract new (...args: any) => any> = (
  ...args: ConstructorParameters<C>
) => InstanceType<C>;

typeof?

由于 JavaScript 的 class 无法很好 tree shaking,加上函数写起来非常方便,所以平时很少用 class。但最近开始折腾 langchainjs,它基本上是跟 python 版本一一对应的,所以写得很向对象也大量用了 class。我想写一个工厂函数传入一些默认参数,却发现拿不到准确的 constructor 的类型。在 tg 群友的指点下搞懂了这里问题,关键就在于区分实例类型和 constructor 的类型。

import { OpenAI } from "langchain/llms/openai";

const createOpenAI: OpenAI["constructor"] = (options, config) => {
  //                       ^? Function
  return new OpenAI({ temperature: 0, ...options }, config);
};

这个问题可以简化成下面的代码:

class Foo {
  constructor(foo: string) {}
}

type A = Foo;
type B = typeof Foo;

type C = A["constructor"];
//   ^? type C = Function

如何从Foo通过类型运算获取到(name: string) => Foo这样的类型,而不是像上面一样拿到的是个Function

在 TypeScript 中typeof除了运行态的语义外,还可以将 Value 转成 Type 用做类型运算。例如const a: typeof 123 = 456。但上面这个AB通过 alias 拿到的都是类型却很不一样。

A 是实例化后的 instanceType,而 B 是构造器 constructor 的 type

在运行态 JavaScript 中实例原型链上的 constructor 字段引用指向Foo,所以A['constructor']会提示Function类型(没有精确的参数类型)。这个 GitHub issue 在讨论这个行为。

const a = new Foo("a");
a.constructor === Foo; // true

再来看下面这个例子:

class Foo {
  constructor(public bar: string) {}
  static baz: 123;
}

type A = Foo;
type B = typeof Foo;

type C = A["bar"]; // string
type D = B["bar"]; // Property 'bar' does not exist on type 'typeof Foo'.(2339)
type E = A["baz"]; // Property 'baz' does not exist on type 'Foo'.(2339)
type F = B["baz"]; // 123

Foo 中声明了两个 field,其中的 baz 是个 static filed,A 由于是实例化过的所以有 bar 没 baz,而 B 由于是 constructor 所以有 baz,没 bar

infer 实现

实际上 TypeScript 文档 中有提到constructor signature,构造函数类型可以写成new (...args: P) => T

TL;DR 中的实现可以不借助 内置的 Utility Types 而用 infer 实现:Playground

type ConstructorType<C> = C extends abstract new (...args: infer P) => infer R
  ? (...args: P) => R
  : never;
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处。

本文链接: https://www.fengkx.top/post/get-constructor-type-from-class-in-typescript/

发布于: 2023-06-16