TypeScript类型体操

2022.09.04
评论

TypeScript类型体操

ts类型体操相当于ts的面试题;但此面试题会涵盖很多特性与常规文档所不常用的关键字,故建议过一遍

类型体操挑战平台

入门篇

Exclude 去除

三元判断是否为我们不需要的类型;联合类型 | 中never会被无视

type _Exclude<T, K> = T extends K ? never : T;
 
type Obj = 'a' | 'b' | 'c';
 
type x1 = Exclude<Obj, 'a'>;
type x2 = _Exclude<Obj, 'a'>;

Pick 检出

type map

type _Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};
 
interface Obj {
  name: string;
  age: number;
  sex: 0 | 1;
}
 
type x1 = Pick<Obj, 'sex' | 'age'>;
type x2 = _Pick<Obj, 'sex' | 'age'>;

Readonly 只读

type map;只读声明

type _Readonly<T> = {
  readonly [P in keyof T]: T[P];
};
 
interface Obj {
  name: string;
  age: number;
  sex: 0 | 1;
}
 
type x1 = Readonly<Obj>;
type x2 = _Readonly<Obj>;

TupleToObject 元组转对象

T约束为只读数组类型;type map

type TupleToObject<T extends readonly any[]> = {
  [P in T[number]]: P;
};
 
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const; // 声明专为枚举
type result = TupleToObject<typeof tuple>;

First/Last 元组的头/尾

infer可在extends后配合三元当占位使用;...展开运算符占位不需要的内容

// type First<T extends unknown[]> = T[0];
type First<T extends unknown[]> = T extends [infer V, ...unknown[]] ? V : never;
type LAST<T extends unknown[]> = T extends [...unknown[], infer V] ? V : never;
 
type arr1 = ['a'];
type arr2 = [3, 2, 1];
 
type head1 = First<arr1>; // expected to be 'a'
type head2 = First<arr2>; // expected to be 3
type head3 = LAST<arr1>; // expected to be 'a'
type head4 = LAST<arr2>; // expected to be 1

Length 元组长度

类似于原型操作

type Length<T extends readonly unknown[]> = T['length'];
 
type tesla = ['tesla', 'model 3', 'model X', 'model Y'];
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT'];
 
type teslaLength = Length<tesla>; // expected 4
type spaceXLength = Length<spaceX>; // expected 5

Awaited promise返回类型

infer取Promise泛型内的类型;递归类型取最终结果

type _Awaited<T> = T extends Promise<infer U> ? _Awaited<U> : T;
 
type Obj1 = Promise<Promise<string>>;
type Obj2 = Promise<string>;
 
type Result1 = Awaited<Obj1>; // string
type Result2 = _Awaited<Obj1>; // string
type Result3 = _Awaited<Obj2>; // string

If bool选择返回

extends可继承于实际类型的值

type If<F extends Boolean, A, B> = F extends true ? A : B;
 
type A = If<true, 'a', 'b'>; // expected to be 'a'
type B = If<false, 'a', 'b'>; // expected to be 'b'

Concat - Array.concat

type Concat<A extends unknown[], B extends unknown[]> = [...A, ...B];
 
type Result = Concat<['x', 1], [2]>; // expected to be ['x', 1, 2]

Includes - Array.includes

TupleToObject 原理;

type Includes<A extends unknown[], B> = B extends A[number] ? true : false;
 
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>; // expected to be `true`

Parameters 函数参数转列表

type _Parameters<T> = T extends (...args: infer U) => void ? U : never;
 
const foo = (arg1: string, arg2: number): void => {};
 
type FunctionParamsType1 = Parameters<typeof foo>; // [arg1: string, arg2: number]
type FunctionParamsType2 = _Parameters<typeof foo>; // [arg1: string, arg2: number]

进阶篇

ReturnType 函数返回值类型

extends后的类型需严格判断,如下 本身fn携带了参数,如果在extends不携带参数就会认为函数类型不同,无法解析U

type _ReturnType<T> = T extends (...args: any) => infer U ? U : never;
// type _ReturnType<T> = T extends () => infer U ? U : never; // useless
 
const fn = (v: boolean) => {
  if (v) return 1;
  else return 2;
};
 
type a1 = ReturnType<typeof fn>; // 应推导出 "1 | 2"
type a2 = _ReturnType<typeof fn>; // 应推导出 "1 | 2"

Omit 忽略

Exclude原理;type map

type _Omit<T, K> = {
  [P in Exclude<keyof T, K>]: T[P];
};
 
interface Obj {
  name: string;
  age: number;
  sex: 0 | 1;
}
 
type x1 = Omit<Obj, 'sex'>;
type x2 = _Omit<Obj, 'sex'>;

Readonly2 只读,携带键参数,不传则默认全部

type map;只读声明;Exclude;联合类型;泛型默认值

type _Readonly2<T, K extends keyof T = keyof T> = {
  readonly [P in K]: T[P];
} & { [P in Exclude<keyof T, K>]: T[P] };
 
interface Obj {
  name: string;
  age: number;
  sex: 0 | 1;
}
 
type x = _Readonly2<Obj, 'sex'>;

DeepReadonly 深度只读

类型递归;需判断当前转换值是否为函数或对象

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends Function ? T[P] : T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
 
type X = {
  x: {
    a: 1;
    b: 'hi';
  };
  y: 'hey';
};
 
type Expected = {
  readonly x: {
    readonly a: 1;
    readonly b: 'hi';
  };
  readonly y: 'hey';
};
 
type Todo = DeepReadonly<X>; // should be same as `Expected`

Chainable 可串联构造器

递归与泛型参数;同名键先删除上一个

type Chainable<T = {}> = {
  option: <K extends string, V>(
    a: K,
    b: V
  ) => Chainable<
    Omit<T, K> & {
      [P in K]: V;
    }
  >;
  get: () => T;
};
 
declare const config: Chainable;
 
const result1 = config.option('foo', 123).option('name', 'type-challenges').option('bar', { value: 'Hello World' }).get();
const result2 = config.option('name', 'another name').option('name', 123).get();
 
// 期望 result1 的类型是:
interface Result1 {
  foo: number;
  name: string;
  bar: {
    value: string;
  };
}
// 期望 result2 的类型是:
 
type Result2 = {
  name: number;
};

TupleToUnion 元组转集合

type TupleToUnion<T extends unknown[]> = T[number];
type Arr = ['1', '2', '3'];
 
type Test = TupleToUnion<Arr>; // expected to be '1' | '2' | '3'

PromiseAll - Promise.all

对于元组和数组类型来说,keyof返回的就是下标;类数组的概念同样在ts类型中有效;

declare function PromiseAll<T extends unknown[]>(
  ps: readonly [...T]
): Promise<{
  [P in keyof T]: Awaited<T[P]>
}>
 
const promise1 = Promise.resolve(3)
const promise2 = 42
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100, 'foo')
})
 
// expected to be `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const)

LookUp 根据字段值寻找其联合类型

联合类型分段特性;基础类型传值

type LookUp<T, K> = T extends { type: K } ? T : never
 
interface Cat {
  type: 'cat'
  breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}
 
interface Dog {
  type: 'dog'
  breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
  color: 'brown' | 'white' | 'black'
}
 
type MyDog = LookUp<Cat | Dog, 'cat'> // expected to be `Cat`

Trim - String.trim

考虑空格和实体字符;模板字符串可以用于string解构

type Empty = ' ' | '\n' | '\t'
 
type TrimLeft<T extends string> = T extends `${Empty}${infer E}`
  ? TrimLeft<E>
  : T
 
type Trim<T> = T extends `${Empty}${infer E}` | `${infer E}${Empty}`
  ? Trim<E>
  : T
 
type trimed1 = TrimLeft<'  Hello World  '> // "Hello World  "
type trimed2 = Trim<'  Hello World  '> // "Hello World"

Capitalize 字符串首字母大写

Trim原理;Uppercase将字符转大写

type Capitalize<T extends string> = T extends `${infer F}${infer L}`
  ? `${Uppercase<F>}${L}`
  : T
type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'

困难篇

BOSS篇