TS 进阶 - 泛型

2023-05-17 20:22:53 浏览数 (2)

# 类型别名中的泛型

类型别名中如果声明了泛型占位,那其实就等价于一个接受参数的函数:

代码语言:javascript复制
type Factory<T> = T | number | string;

// 伪代码理解
// function Factory(typeArg) {
//   return typeArg | number | string;
// }

类型别名中的泛型大多是用来进行工具类型封装:

代码语言:javascript复制
type Stringify<T> = {
  [K in keyof T]: string;
}

type Clone<T> = {
  [K in keyof T]: T[K];
}

TypeScript 内置工具类型 Partial 就是使用了类型别名:

代码语言:javascript复制
type Partial<T> = {
  [K in keyof T]?: T[K];
}

interface IFoo {
  prop1: string;
  prop2: number;
  prop3: boolean;
}

type PartialIFoo = Partial<IFoo>;

// 等价于
// interface PartialIFoo {
//   prop1?: string;
//   prop2?: number;
//   prop3?: boolean;
// }

在条件类型参与的情况下,通常泛型会被作为条件类型中的判断条件(T extends ConditionType extends T)以及返回值。这也是筛选类型需要依赖的能力之一。

代码语言:javascript复制
type IsEqual<T> = T extends true ? 1 : 0;

type IsEqualTrue = IsEqual<true>; // 1
type IsEqualFalse = IsEqual<false>; // 0

# 泛型约束与默认值

泛型也有默认值的设定:

代码语言:javascript复制
type Factory<T = boolean> = T | number | string;

// 在调用时不带任何参数,就会使用默认值
const foo: Factory = false;

// 伪代码理解
// function Factory(typeArg = boolean) {
//   return typeArg | number | string;
// }

泛型约束,可以要求传入这个工具类型的泛型必须符合某些条件,否则就拒绝进行后面的逻辑:

代码语言:javascript复制
type ResStatus<ResCode extends number> = ResCode extends  10000 | 10001 | 10002
  ? 'success'
  : 'error';

type Res1 = ResStatus<10000>; // 'success'
type Res2 = ResStatus<10003>; // 'error'

type Res3 = ResStatus<'10000'>; // Type 'string' does not satisfy the constraint 'number'.

extends 关键字用来约束传入的泛型参数必须符合要求。A extends B 表示 AB 的子类型,或者说 AB 的类型更精确、更复杂(实现细节上)。

给泛型参数声明一个默认值,可以让这个泛型参数变成可选的:

代码语言:javascript复制
type ResStatus<ResCode extends number = 10000> = ResCode extends  10000 | 10001 | 10002
  ? 'success'
  : 'error';

type Res1 = ResStatus<10000>; // 'success'
type Res2 = ResStatus<10003>; // 'error'
type Res3 = ResStatus; // 'success'

# 多泛型关联

不仅可以同时传入多个泛型参数,还可以让这几个泛型参数之间也存在联系:

代码语言:javascript复制
type Conditional<Type, Condition, TruthyResult, FalsyResult> = 
  Type extends Condition ? TruthyResult : FalsyResult;

type Result1 = Conditional<'Cell', string, 'passed!', 'rejected!'>; // 'passed!'

type Result2 = Conditional<100, string, 'passed!', 'rejected!'>; // 'rejected!'

多泛型参数像接受多个参数的函数,其内部运行逻辑(类型操作)会更加抽象,表现在参数(泛型参数)需要进行的逻辑运算(类型操作)会更加复杂。

# 对象类型中的泛型

由于泛型提供了对类型结构的复用能力,也会经常在对象类型结构中使用泛型:

代码语言:javascript复制
interface IRes<IData = unknown> {
  code: number;
  error?: string;
  data: IData;
}

interface IUserProfileRes {
  name: string;
  age: number;
}

function fetchUserProfile(): Promise<IRes<IUserProfileRes>> {}

在分页结构中,也常用到泛型嵌套:

代码语言:javascript复制
interface IPaginationRes<IItem = unknown> {
  data: IItem[];
  total: number;
  page: number;
  hasMore: boolean;
}

function fetchUserProfileList(): Promise<IRes<IPaginationRes<IUserProfileRes>>> {}

# 函数中的泛型

代码语言:javascript复制
function handle<T>(input: T): T {}

T 会被自动地填充为这个参数的类型,不再需要预先确定参数的可能类型,在返回值与参数类型关联的情况下,也可以通过泛型参数来进行运算。

在基于参数类型进行填充泛型时,其类型信息会被推断到尽可能精确的程度。因为传入一个值时,这个值是不会再被修改的,因此可以推导到最精确的程度。如果使用变量作为参数,那么会使用这个变量标注的类型。

代码语言:javascript复制
function handle<T>(input: T): T {}

const developer = 'Cell';
let age = 18;

handle(developer); // 'Cell' 填充字面量类型为 'Cell'
handle(age); // number 填充变量类型为 number

箭头函数中的泛型:

代码语言:javascript复制
const handle = <T>(input: T): T => {};

在 tsx 中,泛型尖括号可能造成报错,可以让他看起来明显一点:

代码语言:javascript复制
const handle = <T extends T>(input: T): T => {};

# Class 中的泛型

Class 中的泛型和函数中的泛型类似,只是函数中泛型参数的消费方式参数和返回值类型,Class 中的泛型消费方则是属性、方法、乃至装饰器等。

Class 内的方法还可以再声明自己独有的泛型参数。

代码语言:javascript复制
class Queue<TElementType> {
  private _list: TElementType[];

  constructor (initial: TElementType[]) {
    this._list = initial;
  }

  enqueue<IType extends TElementType>(ele: IType): TElementType[] {
    this._list.push(ele);
    return this._list;
  }

  enqueueWithUnkonwType<IType>(element: IType): (TElementType | IType)[] {
    this._list.push(element);
    return this._list;
  }

  dequeue(): TElementType [] {
    this._list.shift();
    return this._list;
  }
}

# 内置方法中的泛型

TypeScript 中为非常多的内置对象都预留了泛型占位,如 Promise

代码语言:javascript复制
function p() {
  return new Promise<boolean>((resolve, reject) => {
    // 填充 Promise 泛型后,其内部的 resolve 方法也自动填充了泛型
    resolve(true);
  });
}

还有数组 Array<T> 当中,其泛型参数代表数组的元素类型,几乎贯穿所有的数组方法:

代码语言:javascript复制
const arr: Array<number> = [1, 2, 3];

arr.push('Cell'); // Argument of type '"Cell"' is not assignable to parameter of type 'number'.

0 人点赞