typescript-常见范式记录

/post/ts-pattern article cover image

《TypeScript全面进阶指南》 的不完全速记笔记.

泛型

泛型作为类型编程中的参数,可以应用在类型别名、对象类型、函数与Class中

类型别名中的泛型

ts
// 类似工厂函数的工厂类型
type Factory<T> = T | number | string;

// 通过索引签名将一个对象类型的所有属性类型设置为string
type Stringify<T> = {
  [k in keyof T]:  string;
}

// 通过类型映射创建一个对应对象全部属性的类型
type Clone<T> = {
  [K in keyof T]: T[K];
}

// 通过类型映射创建一个可选的变种类型
type Partial<T> = {
  [P in keyof T]?: T[P];
}

// 条件类型: T enxtends Condition | Type extends T
type IsEqual<T> = T extends true ? 1 : 2;
type A = IsEqual<true>; // 1
type B = IsEqual<false>; // 2
type C = IsEqual<'something'>; // 2

泛型约束与默认值

ts
// 与函数默认值类似
type Factory<T = boolean> = T | number | string;
const foo: Factory = false;

// 使用extends进行泛型约束
type ResStatus<ResCode extends number> = ResCode extends 1000 | 1001 | 1002 ? 'success' : 'failure';
type Res1 = ResStatus<1000>;   // 'success'
type Res2 = ResStatus<1001>;   // 'failure'
type Res3 = ResStatus<'1002'>; // 类型'string'不满足约束'number'

// 类型约束增加默认值
type ResStatus2<ResCode extends number = 1000> = ResCode extends 1000 | 1001 | 1002 ? 'success' : 'failure';
type Res4 = ResStatus2;

多泛型关联

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

ts
// 多泛型参数多用于需要在内部进行逻辑运算的情形
type Conditional<Type, Condition, TruthyResult, FalsyResult> = Type extends Condition ? TruthyResult : FalsyResult;
type Result1 = Conditional<'huhu', string, 'success!', 'rejected!'>; // success!
type Result2 = Conditional<'huhu', number, 'success!', 'rejected!'>; // rejected!

/**
* 多泛型参数之间的依赖,是指在后续泛型参数中,使用前面的泛型参数作为约束或默认值
* 下例有如下含义:
*   当只传入一个泛型参数时,其第二个泛型参数会被赋值为此参数,而第三个则会赋值为第二个泛型参数,相当于均使用了这唯一传入的泛型参数
*   当传入两个泛型参数时,第三个泛型参数会默认赋值为第二个泛型参数的值
*/
type ProcessInput<
  Input,
  SecondInput extends Input = Input,
  ThirdInput extends Input = SecondInput
> = number;

对象类型中的泛型

ts
// 预留实际响应数据的泛型坑位实现通用数据结构
interface IRes<TData = unknown> {
  code: number;
  error?: string;
  data: TData;
}

/* 下面是使用泛型嵌套的例子 */
interface IUserProfileRes {
  name: string;
  homepage: string;
  avatar: string;
}
function fetchUserProfile(): Promise<IRes<IUserProfileRes>> {
  //...
}

type StatusSucceed = boolean;
function handleOperation(): Promise<IRes<StatusSucceed>> {
  //...
}

// 分页结构数据情形
interface IPaginationRes<TItem = unknown> {
  data: TItem[];
  page: number;
  totalCount: number;
  hasNextPage: boolean;
}
function fetchUserProfileList(): Promise<IRes<IPaginationRes<IUserProfileRes>>> {
  //...
}

函数中的泛型

ts
/**
*  当一个函数接受多个参数并对它进行处理时,按照以往的学习可以使用如下方式
*/

// 使用any,缺点:失去类型推断能力,不推荐
function handler(input: any): any {}

// 使用联合类型,缺点:每次增加类型都需要手动添加
function handler(input: string | number | {}): string | number | {} {}

// 使用函数重载,缺点:和手动添加联合类型类似机械且难以维护
function handler(input: string): string
function handler(input: number): number
function handler(input: {}): {}
function handler(input: string | number | {}): string | number | {} {}

// 使用泛型,既解决了类型预设的麻烦(范性在调用时被填充)又可以对返回值和参数进行关联运算
function handler<T>(input: T): T {
  return input;
}

const name = 'someName';
const age = 17;

handler(name); // infer to string
handler(age); // infer to number

// 范型参数被内部逻辑消费
function handler<T>(payload: T): Promise<[T]> {
  return new Promise<[T]>((res, rej) => {
    res([payload]);
  });
}

class中的范型

与函数范型不同的是,函数是参数和返回值对范型进行消费。而Class则是属性、方法、乃至装饰器等

ts
class Queue<TElementType> {
  private _list: TElementType[];

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

  // 入队一个队列泛型子类型的元素
  enqueue<TType extends TElementType>(ele: TType): TElementType[] {
    this._list.push(ele);
    return this._list;
  }

  // 入队一个任意类型元素(无需为队列泛型子类型)
  enqueueWithUnknownType<TType>(element: TType): (TElementType | TType)[] {
    return [...this._list, element];
  }

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

内置方法的范型

ts
// 为Promise设置范型时,范型会自动填充到resolve的参数中
function p() {
  return new Promise<boolean>((resolve, reject) => {
    resolve(true);
  });
}

// 在Typescript内部Promise这么定义
interface PromiseConstructor {
    resolve<T>(value: T | PromiseLike<T>): Promise<T>;
}
declare var Promise: PromiseConstructor;

// 数组Array<T>
const arr: string[] = ['aaa', 'bbb', 'ccc'];
arr.push[1]; // Argument of type 'number' is not assignable to parameter of type 'string'
arr.find(() => false); // number | undefined

// 如果初始值的类型与数组元素类型一致,则使用数组的元素类型进行填充
arr.reduce((prev, curr, idx, arr) => {
  return prev;
}, 1);

// reduce 的泛型参数会默认从这个初始值推导出的类型进行填充,如这里是 never[]
// 报错:不能将 number 类型的值赋值给 never 类型
arr.reduce((prev, curr, idx, arr) => {
  return [...prev, curr]
}, []);

// 手动指定类型
arr.reduce<number[]>((prev, curr, idx, arr) => {
  return prev;
}, []);