玩转Typescript(四):TypeScript中常用的工具类型

309 阅读4分钟

这是我参与11月更文挑战的第 4 天,活动详情查看:2021最后一次更文挑战

前面我们学习了Typescript的基础类型、泛型和接口,能满足书写Typescript代码的大多数需求了。

比如有这样一个需求:定义一个MyInt接口,并创建相应的man对象。

interface MyInt {
  name: string
  age?: number
}
const man: MyInt = {
  name: 'Tom',
  age: 18
}
console.log(man)

假如后来我们想让name属性是只读的,那么我们可以怎么操作呢?

除了重新定义一个接口,有没有更方便的方法呢?

答案当然是有的。我们可以借助Typescript提供的Partial<T>Required<T>Readonly<T>等工具函数去实现。这些工具函数的源码可参考TypeScript 项目的./node_modules/typescript/lib/lib.es5.d.ts文件。

Partial<T>

把某个接口类型中定义的属性变成可选。是TypeScript 2.1版本发布的。

demo1. 改变接口中的属性

interface Person {
  age: number;
  name: string;
}
const jerry: Person = {
  age: 10,
  name: 'Jerry'
};
const tom: Partial<Person> = {
  name: 'Tom'
};
console.log(jerry)
console.log(tom)

demo2. 对一个数据对象做局部更新

interface DataModel {
  name: string
  age: number
  address: string
}
let store: DataModel = {
  name: '',
  age: 0,
  address: ''
}
function updateStore (store: DataModel, payload: Partial<DataModel>): DataModel {
  return {
    ...store,
    ...payload
  }
}
store = updateStore(store, {
  name: 'tom',
  age: 18
})
console.log(store)

源码实现

type Partial<T> = {
  [P in keyof T]?: T[P]
}

实现Partial<T>

interface Person {
  age: number;
  name: string;
}
const jerry: Person = {
  age: 10,
  name: 'Jerry'
};
type JerryPartial<T> = {
  [P in keyof T]?: T[P];
};
const tom: JerryPartial<Person> = {
  name: 'Tom'
};

Required<T>

把所有可选属性变成必选属性。

interface Props {
  a?: number;
  b?: string;
}
const obj: Props = { a: 5 };
 // ok
const obj2: Required<Props> = { a: 5 };
// Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.

源码实现

type Required<T> = {
  [P in keyof T]-?: T[P];
};

Readonly<T>

在 javascript 中,如果给 const 变量赋值为一个引用类型(比如一个对象),是可以修改属性值的,不能修改的是变量中存储的引用。如果要实现对象属性值的不可变,可以使用 Object.freeze

Readonly<T>是将类型 T 中包含的属性设置为 readonly,并返回一个新类型。这里 readonly 是只读的意思,在初始化后就不能再修改值。可以结合 const 关键字实现引用类型属性值为常量的目的。

interface Todo {
  title: string;
}
const todo: Readonly<Todo> = {
  title: "Delete inactive users",
};
todo.title = "Hello";
// Cannot assign to 'title' because it is a read-only property.

源码实现

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

Record<Keys, Type>

定义一个对象的 key 的类型和 value 的类型。

比如我需要一个对象,有 ABC 三个属性,属性的值必须是数字,那么就这么写:

type keys = 'A' | 'B' | 'C'
const result: Record<keys, number> = {
  A: 1,
  B: 2,
  C: 3
}

源码实现

/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

这里keyof any代表可以作为对象索引的任何值,相当于string | number | symbol

Pick<Type, Keys>

从类型 Type 中,挑选一组属性组成一个新的类型返回。这组属性由 Keys 限定, Keys 是字符串或者字符串并集。

interface Person {
  name: string
  age: number
  id: string
}
type man = Pick<Person, 'name' | 'age'>
let a: man = {
  name: 'hh',
  age: 18
}
console.log(a)

源码实现

/**
 * From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

K extends keyof T ,表示 K 是 keyof T 的子集。返回的类型的键需要满足[P in K],值类型满足T[P]。

Exclude<Type, ExcludedUnion>

Constructs a type by excluding from Type all union members that are assignable to ExcludedUnion.

声明一个type,在Type 中排除了ExcludedUnion的所有成员。

type T0 = Exclude<"a" | "b" | "c", "a">; 
// type T0 = "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
// type T1 = "c"
type T2 = Exclude<string | number | (() => void), Function>;
// type T2 = string | number

源码实现

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

这里的T extends U表示条件类型,当T“继承了”U时,返回never,否则返回T。

Extract<Type, Union>

提取Type中可分配到Union的所有成员。

type ExtractedType = Extract<"x" | "y" | "z", "x" | "y">;
// "x" | "y"
type T1 = Extract<string | number | (() => void), Function>;
// type T1 = () => void

用来提取两个类型的公有属性名会非常的合适:

interface Human {
  id: string;
  name: string;
  surname: string;
}
interface Cat {
  id: string;
  name: string;
  sound: string;
}
type CommonKeys = Extract<keyof Human, keyof Cat>; // "id" | "name"

源码实现

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

Omit<Type, Keys>

构造一个类型,这个类型包含类型 Type中除了 Keys 之外的其余属性。 Keys是一个字符串或者字符串并集。

interface Person {
  name: string
  age: number
  work: string
}
type PersonNoWork = Omit<Person, 'work'>
const man: PersonNoWork = {
  name: "hh",
  age: 18
};
console.log(man);

源码实现

/**
 * Construct a type with the properties of T except for those in type K.
 */
type Omit<T, K extends keyof any> = {
  [P in Exclude<keyof T, K>]: T[P];
}
// or
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

NonNullable<Type>

Constructs a type by excluding null and undefined from Type.

type T0 = NonNullable<string | number | null | undefined>;
// type T0 = string | number

源码实现

/**
 * Exclude null and undefined from T
 */
type NonNullable<T> = T extends null | undefined ? never : T;

参考