作者: 徐海强


ts 内置类型

Partial

将其变为可选

  1. type Partial<T> = {
  2. [P in keyof T]?: T[P];
  3. };

这里稍微解释一下

keyof T 拿到 T 所有属性名, 然后 in 进行遍历, 将值赋给 P, 最后 T[P] 取得相应属性的值.

例子:

  1. interface People {name: string}
  2. Partial<People> => {name?: string}

其是有局限性的,只能处理一层

  1. interface People {
  2. name: string;
  3. person: {name: string; name1: string}
  4. }
  5. type NewPeople = Partial<People>
  6. const jack: NewPeople = {
  7. name: 'jack',
  8. person: {
  9. name: 'son'
  10. }
  11. }
  12. type PowerPartial<T> = {
  13. [U in keyof T]?: T[U] extends object
  14. ? PowerPartial<T[U]>
  15. : T[U]
  16. };

Readonly

只读

  1. type Readonly<T> {readonly [P in keyof T]: T[P]}

当然这也只能一层 如上面 Partial 例子来看 jack.person.name 是可以直接修改的。 也可以和 Partial 结合起来

  1. type ReadonlyPartial<T> = { readonly [P in keyof T]?: T[P] };

Required

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

上面的-?, 这里很好理解就是将可选项代表的 ? 去掉, 从而让这个类型变成必选项. 与之对应的还有个+? , 这个含义自然与-?之前相反, 它是用来把属性变成可选项的.

Pick

从 T 中取出 一系列 K 的属性

  1. type Pick<T, K extends keyof T> = {
  2. [P in K]: T[P];
  3. };

例子:

  1. type NewPerson = Pick<People, 'name'>;

Exclude

从 T 中移除 一系列 U 的属性

  1. type Exclude<T, U> = T extends U ? never : T;
  2. type T = Exclude<1 | 2, 1 | 3>

与 Exclude 类似的 Extract<T, U>(取交集)

  1. type Extract<T, U> = T extends U ? T : never;
  2. type T = Extract<'a'|'b'|'c'|'d' ,'b'|'c'|'e' >

将 Pick 和 Exclude 结合起来实战

  1. interface Api {
  2. name: string;
  3. age: number
  4. }
  5. interface CustomApi extends Api {
  6. name: number;
  7. }
  8. interface CustomApi1 extends Pick<Chicken, 'age'> {
  9. name: number;
  10. }
  11. interface CustomApi2 extends Pick<Api, Exclude<keyof Api, 'name'>> {
  12. name: number;
  13. }
  14. interface CustomApi3 extends Omit<Api, 'name'> {
  15. name: number;
  16. }

类似Exclude作用的 还有 NonNullable, 将 null | undefined排除

  1. type NonNullable<T> = T extends null | undefined ? never : T;
  2. type Test = '111' | '222' | null;
  3. type NewTest = NonNullable<Test>;

Omit

未包含

  1. type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
  2. type Foo = Omit<{name: string, age: number}, 'name'>

Record

标记对象的 key value 类型

  1. type Record<K extends keyof any, T> = {
  2. [P in K]: T;
  3. };
  4. const user: Record<'name'|'email', string> = {
  5. name: '',
  6. email: ''
  7. }
  8. function mapObject<K extends string | number, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>;
  9. function mapObject(): any {}
  10. const names = { foo: "hello", bar: "world", baz: "bye" };
  11. const lengths = mapObject(names, s => s.length);
  12. type newNames = typeof lengths

ReturnType

反解

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

其实这里的 infer R 就是声明一个变量来承载传入函数签名的返回值类型(反解), 简单说就是用它取到函数返回值的类型方便之后使用. 举个例子来理解infer

  1. type PromiseType<T> = (args: any[]) => Promise<T>;
  2. type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;
  3. async function stringPromise() {
  4. return "string promise";
  5. }
  6. type extractStringPromise = UnPromisify<typeof stringPromise>;

ReturnType demo

  1. function TestFn() {
  2. return 'test';
  3. }
  4. type Test = ReturnType<typeof TestFn>;

和上述差不多了 我们可以依葫芦画瓢 个 PromiseType

  1. type PromiseType<T extends Promise<any>> = T extends Promise<infer R> ? R : never;
  2. type Test = PromiseType<Promise<string>>

再结合深入一点

  1. type PromiseReturnType<T extends () => any> = ReturnType<T> extends Promise<
  2. infer R
  3. >
  4. ? R
  5. : ReturnType<T>
  6. async function test() {
  7. return { a: 1, b: '2' }
  8. }
  9. type Test = PromiseReturnType<typeof test>

Parameters

获取一个函数的所有参数类型

上面的ReturnType认识了infer, 这里直接放源码和 demo 了

  1. type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
  2. interface IPerson {name: string}
  3. interface IFunc {
  4. (person: IPerson, count: number): boolean
  5. }
  6. type P = Parameters<IFunc>
  7. const person1: P[0] = {
  8. name: '1'
  9. }

ConstructorParameters

类似于 Parameters<T>, ConstructorParameters 获取一个类的构造函数参数

  1. type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
  2. type DateConstrParams = ConstructorParameters<typeof Date>
  3. interface DateConstructor {
  4. new (value: number | string | Date): Date;
  5. }

业务结合,自定义

1. 有的时候重写属性

  1. interface Test {
  2. name: string;
  3. age: number;
  4. }
  5. interface Test2 extends Test{
  6. name: Test['name'] | number
  7. }

实现 将 T 中的 key 对应的 value 设为any 其就不冲突了

  1. type Weaken<T, K extends keyof T> = {
  2. [P in keyof T]: P extends K ? any : T[P];
  3. }
  4. interface Test2 extends Weaken<Test, 'name'>{
  5. name: Test['name'] | number
  6. }

当然上面更极端的做法也可以是 排除再重写

  1. interface Test2 extends Omit<Test, 'name'> {
  2. name: Test['name'] | number
  3. }

2. 将联合类型|转成交叉类型&

  1. type UnionToIntersection<U> = (U extends any
  2. ? (k: U) => void
  3. : never) extends ((k: infer I) => void)
  4. ? I
  5. : never
  6. type Test = UnionToIntersection<{ a: string } | { b: number }>
  7. type Weird = UnionToIntersection<string | number | boolean>

可能有人会懵,首先得理解 Distributive conditional types, 举个例子 T extends U ? X : Y 中,当 TA | B 时,会拆分成 A extends U ? X : Y | B extends U ? X : Y;再结合infer同一类型变量的多个候选类型将会被推断为交叉类型,参考举个例子:

  1. type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
  2. type T10 = Foo<{ a: string, b: string }>;
  3. type T11 = Foo<{ a: string, b: number }>;
  4. type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
  5. type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>;
  6. type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>;

综上:

  1. type Result = UnionToIntersection<T1 | T2>;
  • 第一步:(U extends any ? (k: U) => void : never) 会把 union 拆分成 (T1 extends any ? (k: T1) => void : never) | (T2 extends any ? (k: T2)=> void : never),即是得到 (k: T1) => void | (k: T2) => void
  • 第二步:((k: T1) => void | (k: T2) => void) extends ((k: infer I) => void) ? I : never,根据上文,可以推断出 I 为 T1 & T2

3. 数组 转换 成 union

  1. const ALL_SUITS = ['hearts', 'diamonds', 'spades', 'clubs'] as const;
  2. type SuitTuple = typeof ALL_SUITS;
  3. type Suit = SuitTuple[number];

ts3.4 新语法 as const创建不可变(常量)元组类型 / 数组,所以 TypeScript 可以安全地采用窄文字类型['a', 'b']而不是更宽('a' | 'b')[]或甚至string[]类型

4. 合并参数返回值类型

  1. function injectUser() {
  2. return { user: 1 }
  3. }
  4. function injectBook() {
  5. return { book: '2' }
  6. }
  7. const injects = [injectUser, injectBook]
  1. type Prettify<T> = [T] extends [infer U] ? { [K in keyof U]: U[K] } : never
  2. type InjectTypes<T extends Array<() => object>> = T extends Array<() => infer P>
  3. ? Prettify<UnionToIntersection<P>>
  4. : never
  5. type result = InjectTypes<typeof injects>

上面看似一堆,我们可以一步步将其拆解

  1. type test = typeof injects;
  2. type test1<T> = T extends Array<() => infer P> ? P : never
  3. type test2 = test1<test>
  4. type test3 = UnionToIntersection<test2>
  5. type test4 = Prettify<test3>

其实上面的过于复杂的,我们可以采用另一个方案

  1. type User = ReturnType<typeof injectUser>
  2. type Book = ReturnType<typeof injectBook>
  3. type result = Prettify<User & Book>

最终可以改成这样

  1. type User = ReturnType<typeof injectUser>
  2. type Book = ReturnType<typeof injectBook>
  3. type result = User | Book

5. 常用内置类型结合封装

  • PartialRecord
  1. interface Model {
  2. name: string;
  3. age: number;
  4. }
  5. interface Validator {
  6. required?: boolean;
  7. trigger?: string;
  8. }
  9. const validateRules: Record<keyof Model, Validator> = {
  10. name: {required: true, trigger: `blur`}
  11. }
  12. type PartialRecord<K extends keyof any, T> = Partial<Record<K, T>>
  13. const validateRules: PartialRecord<keyof Model, Validator> = {
  14. name: {required: true, trigger: `blur`}
  15. }
  • DeepPartial
  1. type RecursivePartial<T> = {
  2. [P in keyof T]?:
  3. T[P] extends (infer U)[] ? RecursivePartial<U>[] :
  4. T[P] extends object ? RecursivePartial<T[P]> :
  5. T[P];
  6. };
  • DeepRequired
  1. type DeepRequired<T> = {
  2. [P in keyof T]-?:
  3. T[P] extends ((infer U)[]|undefined) ? DeepRequired<U>[] :
  4. T[P] extends (object|undefined) ? DeepRequired<T[P]> :
  5. T[P]
  6. }

6. 挑选出readonly字段

  1. type IfEquals<X, Y, A=X, B=never> =
  2. (<T>() => T extends X ? 1 : 2) extends
  3. (<T>() => T extends Y ? 1 : 2) ? A : B;
  4. type ReadonlyKeys<T> = {
  5. [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>
  6. }[keyof T];
  7. type A = {
  8. readonly a: string
  9. b: number
  10. }
  11. type B = ReadonlyKeys<A>

以下参考轮子哥的解释:首先应该解释一下ReadonlyKeys里面[Q in P]的意思。P他是一个字符串,不是一个字符串的集合,所以[Q in P]实际上就是P。如果你直接写{P:T[P]}的话,你得到的是一个拥有成员变量"P"的对象,而{[Q in P]:T[P]}拿到的是变量P在这里的值(也就是 “a” 或者 “b”),而且他还把有没有 readonly的这个属性给带了回来。如果把ReadonlyKeys改成这样的类型 type

  1. type ReadonlyKeys2<T> = {
  2. [P in keyof T]-?: { [Q in P]: T[P] }
  3. };

那我们会得到为

  1. ReadonlyKeys2<A> {
  2. readonly a: {readonly a:string};
  3. b: {b:number};
  4. }

然后我们就去调用 IfEquals。在这里我需要指出,<T>()=>T extends X ?1:2的优先级是<T>()=>(T extends X ?1:2),在T是个自由变量的情况下,我们比较的是X 和 Y究竟是不是同一个类型。比较两个泛型类型,又没有办法拿到确切的值来计算,只能直接比较一下表达式是否相同。

  1. {
  2. readonly a : (<T>()=>T extends {readonly a:string} ? 1 : 2) extends (<T>()=>T extends {a:string} ? 1 : 2) ? never : 'a';
  3. b : (<T>()=>T extends {b:number} ? 1 : 2) extends (<T>()=>T extends {b:number} ? 1 : 2) ? never : 'b';
  4. }['a' | 'b']

ps: 这里提一下 -readonly其实就是将 T 的所有属性的 readonly 移除

然后我们来计算一下显然,以下两个表达式是不一样的<T>()=>T extends {readonly a:string} ? 1 : 2<T>()=>T extends {a:string} ? 1 : 2而以下两个表达式是一样的<T>()=>T extends {b:number} ? 1 : 2<T>()=>T extends {b:number} ? 1 : 2一样会得到never,不一样就得到一个字符串,这里原理是Conditional Types在有变量没被 resolve 时,extends部分必须完全一致才算相等 于是上面的类型就被简化为

  1. {
  2. readonly a:'a';
  3. b:never;
  4. }['a' | 'b']

显然 never 就代表没有,因此你得到了’a’。 如果您阅读之后有所帮助,欢迎点赞,如果有更好的意见和批评,欢迎指出!
https://juejin.cn/post/6895538129227546638#heading-0