映射类型 -- Typescript类型编程篇(3)

748 阅读2分钟

ts提供了映射类型,用于重用和修改已存在的类型。

假如我们有一个Person接口,我们希望把Person内部所有的成员都变成可选。在不借助映射类型时,我们可以会这样做:

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

interface OptionalPerson {
  name?: string;
  age?: number;
}

这样既繁琐,也没有做到类型复用。而借助映射类型:

type OptionalPerson = { [K in keyof Person]?: Person[K] };

映射类型使用形如[K in KEYS]的形式遍历类型中所有属性名,此时的KEYS是通过keyof Person返回所有属性名所组成的联合类型。并通过添加?操作符将所有属性变为可选类型。

如果我们希望把Person所有属性变成只读的:

type ReadonlyPerson = { readonly [K in keyof Person]: Person[K] };

+/-操作符

如果Person中所有属性就是只读类型,那么我们有没有办法去掉只读限制呢?我们可以使用+或者-操作符表示添加或者删除某种限制。

interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}

type Person = { -readonly [K in keyof ReadonlyPerson]: Person[K] };

如果Person中所有属性就是可选类型,将所有可选限制去掉:

interface OptionalPerson {
  name?: string;
  age?: number;
}

type Person = { [K in keyof OptionalPerson]-?: Person[K] };

readonly等价于:+readonly?等价于:+?

深层映射

如果对象结构有很多层。使用上述的例子只能遍历和修改第一层属性;如果深层映射,我们可以递归调用映射类型。如希望一个类型所有层级的属性都为可选:

interface DeepObj {
  name: string;
  age: number;
  deepAttributes: {
    prop1: string;
    prop2: string;
    obj: {
      other: number;
    };
  };
}

type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

type PartialDeepOjb = DeepPartial<DeepObj>;

DeepPartial中,我们会使用条件类型判断当前的属性类型是否是object,如果是则递归映射,否则直接使用当前类型。