Skip to content

构造函数与异步相关类型工具

构造函数相关类型工具

ConstructorParameters<Type>

ConstructorParameters<Type> 从构造函数类型中获取构造函数参数的元组类型。

基本用法

typescript
class User {
  constructor(
    public id: number,
    public name: string,
  ) {}
}

type ConstructorParamsType1 = ConstructorParameters<typeof User>; // [id: number, name: string]

实现原理

对于 ConstructorParameters 的实现其实和之前的 ReturnType 十分类似,只不过现在我们需要针对构造函数类型进行处理:

typescript
type MyConstructorParams<T extends abstract new (...args: any[]) => any> = T extends abstract new (...args: infer R) => any ? R : never;

type ConstructorParamsType2 = MyConstructorParams<typeof User>;

应用场景

我们可以扩展一个场景实现一下效果:

typescript
class Book {
  title: string;
  content?: string;
  constructor(title: string) {
    this.title = title;
  }
}

class CreateInstance<T extends new (...args: any[]) => any> {
  private ClassConstructor: T;
  constructor(classConstructor: T) {
    this.ClassConstructor = classConstructor;
  }

  getInstance(...args: ConstructorParameters<T>): InstanceType<T> {
    return new this.ClassConstructor(...args);
  }
}

const instanceCreator = new CreateInstance(Book);
const book = instanceCreator.getInstance('Typescript类型全解');

InstanceType<Type>

InstanceType<Type> 可以通过构造函数类型得到其实例对象类型。

基本用法

typescript
class User {
  constructor(
    public id: number,
    public name: string,
  ) {}
}

type U = InstanceType<typeof User>;

实现原理

要去实现这个类型工具也非常简单:

typescript
type MyInstanceType<T extends abstract new (...args: any[]) => any> = T extends abstract new (...args: any[]) => infer R ? R : never;

type InstanceUser = MyInstanceType<typeof User>;

注意typeof User 中的 User 代表的是类本身(构造函数),而不仅仅是类型。


深度探讨:TS 中可以直接写构造函数吗?

有这个思考,说明你对类型系统的认知还不够明确!

函数是具有二义性的,因此 ES6 中才有了箭头函数和类的区分。在 TS 中:

  • 如果要使用类,应该使用 class 关键字。
  • 如果要使用函数,那么它就是一个普通函数。

我们可以接着这个“错误”思考,来深入认知一下类的类型。

typescript
function Person(id: number, name: string, age: number) {
  this.id = id;
  this.name = name;
  this.age = age;
}

首先,这种写法在 TS 中会直接报错。要尝试运行,至少需要将 noImplicitThis 设置为 false。但即使这样,这个在 JS 中认知的构造函数还不能直接 new

typescript
const person = new Person(1, 'John Doe', 30); // Error: 其目标缺少构造签名的 "new" 表达式

因为编译器不知道 new 出来的是什么。我们可以使用简单的断言来屏蔽错误:

typescript
const person = new (Person as any)(1, 'John Doe', 30);

但这样得到的 personany 类型,没有任何类型提示。更关键的是,使用 InstanceType 也是徒劳的:

typescript
type PersonInstance = InstanceType<typeof Person>; // Error: 类型不匹配

原因typeof Person 获取的仅仅是一个函数类型,而不是构造函数类型。

解决方案:人为构造类型

如果我们必须使用这种旧式写法,可以通过以下方式填补类型缺陷:

typescript
interface Person {
  id: number;
  name: string;
  age: number;
}

type PersonConstructor = new (id: number, name: string, age: number) => Person;

// 或者使用接口形式定义构造签名:
// interface PersonConstructor {
//   new(id: number, name: string, age: number): Person;
//   readonly prototype: Person;
// }

type PersonInstance = InstanceType<PersonConstructor>;

const person: PersonInstance = new (Person as any)(1, 'John Doe', 30);

异步相关类型工具:Awaited<Type>

Awaited<Type> 用于获取 Promise 中的类型(模拟 await.then() 的解包行为)。

基本用法

typescript
type T = Awaited<Promise<string>>; // string

应用场景:处理第三方接口

当从第三方库获取的函数返回 Promise 时,Awaited 可以帮我们提取出实际的数据类型。

typescript
interface User {
  id: number;
  firstName: string;
  lastName: string;
  age: number;
}

async function fetchUser(): Promise<User> {
  const data = await fetch('https://api.example.com/user').then((res) => res.json());
  return data;
}

// 提取 fetchUser 的返回值类型
type UserFetch = Awaited<ReturnType<typeof fetchUser>>;

const user: UserFetch = {
  id: 1,
  firstName: 'yuan',
  lastName: 'jin',
  age: 18,
};

实现原理

Awaited 的内部实现利用了递归条件类型:

typescript
type MyAwaited<T> = T extends null | undefined
  ? T
  : T extends object & { then(onfulfilled: infer F, ...args: infer _): any }
    ? F extends (value: infer V, ...args: infer _) => any
      ? MyAwaited<V> // 递归解包
      : never
    : T;

递归与嵌套处理

Awaited 会递归解包,直到获取到非 Promise 类型:

typescript
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number
type C = Awaited<boolean | Promise<number>>; // number | boolean
type D = Awaited<number>; // number

进阶:约束为 Promise 类型

如果希望传入的必须是 Promise 类型,否则报错,可以结合 PromiseLike 进行封装:

typescript
type StrictAwaited<T extends PromiseLike<any>> = Awaited<T>;

type F = StrictAwaited<number>; // Error: 类型“number”不满足约束“PromiseLike<any>”