Typecript的复合类型 - 进阶篇

2,100 阅读3分钟

背景知识

Typescript的复合类型分为两类,setmap, set是指一个无序的、无重复元素的集合,map就跟咱js的对象一样。

set 和 map相互转换

// map to set

interface IMap = {
    a: string,
    b: string
}

type sizeIns = keyof IMap; // 'a' | 'b'


// set to map

interface IMapIns = {
    [k in sizeIns]: number
}

// output

// interface IMapIns = {
//    a: number,
//    b: number
// }

类型映射

TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型。 在映射类型里,新类型以相同的形式去转换旧类型里每个属性。

类型映射 - 同态变换

它们的共同点是只接受一个传入类型,生成的类型中key都来自于keyof传入的类型,value都是传入类型的value的变种。

type Partial<T> = { [P in keyof T]?: T[P] }    // 将一个map所有属性变为可选的
type Required<T> = { [P in keyof T]-?: T[P] }    // 将一个map所有属性变为必选的
type Readonly<T> = { readonly [P in keyof T]: T[P] }    // 将一个map所有属性变为只读的
type Mutable<T> = { -readonly [P in keyof T]: T[P] }    // ts标准库未包含,将一个map所有属性变为可写的

tips

  • -?: 是将可选项代表的?去掉, 从而让这个类型变成必选项。
  • +?: 与-?相反,把属性变成可选的
  • -readonly: 将readonly属性移除

在进行同态变换时,TS会先复制一遍传入参数的属性修饰符,再应用定义的变换。

interface Fruit {
    readonly name: string
    size: number
}
type PF = Partial<Fruit>;    // PF.name既只读又可选,PF.size只可选

类型映射 - 其他类型变换

set 转换成 map

type Record<K extends keyof any, T> = { [P in K]: T };

type Size = 'small' | 'default' | 'big';
/*
{
    small: number
    default: number
    big: number
}
 */
type SizeMap = Record<Size, number>;

保留map的一部分

type Pick<T, K extends keyof T> = { [P in K]: T[P] };
/*
{
    default: number
    big: number
}
 */
type BiggerSizeMap = Pick<SizeMap, 'default' | 'big'>;

删除map的一部分

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
/*
{
    default: number
}
 */
type DefaultSizeMap = Omit<BiggerSizeMap, 'big'>;

保留set的一部分

type Extract<T, U> = T extends U ? T : never;

type Result = 1 | 2 | 3 | 'error' | 'success';
type StringResult = Extract<Result, string>;    // 'error' | 'success

删除set的一部分

type Exclude<T, U> = T extends U ? never : T;
type NumericResult = Exclude<Result, string>;    // 1 | 2 | 3

获取函数返回值的类型

但要注意不要滥用这个工具类型,应该尽量多手动标注函数返回值类型。理由开篇时提过,契约高于实现。 用ReturnType是由实现反推契约,而实现往往容易变且容易出错,契约则相对稳定。另一方面,ReturnType过多也会降低代码可读性。

type ReturnType<T> = T extends (...args: any[]) => infer R ?  R : any;

function f() { return { a: 3, b: 2}; }
/*
{
    a: number
    b: number
}
 */
type FReturn = ReturnType<f>

tips

infer的作用是让TypeScript自己推断,并将推断的结果存储到一个临时名字中,并且只能用于extends语句中。它与泛型的区别在于,泛型是声明一个“参数”,而infer是声明一个“中间变量”。