TinyScript / notes

0 stars 0 forks source link

Typescript类型体操 #2

Open TinyScript opened 2 years ago

TinyScript commented 2 years ago

一、Pick 选择属性

interface Todo {
  title: string
  description: string
  completed: boolean
}
Pick<Todo, 'title'> // => { title: string }

// 实现
type MyPick<T, K extends keyof T> = { [k in K]: T[k] }

MyPick<Todo, 'title'> // => { title: string }

二、Readonly 只读

interface Todo {
  title: string
  description: string
  completed: boolean
  meta: {
    author: string
  }
}

const todo: Readonly<Todo> ​= { .... }
todo.title = 1 // error

// 实现
type MyReadonly<T> = { readonly [k in keyof K]: T[k] }
const todo: MyReadonly<Todo> ​= { .... }
todo.title = 1 // => error

三、Tuple to Object 元组转对象

// 什么是元组?https://ts.xcatliu.com/advanced/tuple.html
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const // 元组

// 实现
type tupleToObject<T extends readonly any[]> = { [val in T[number]: val }
const obj: tupleToObject<tuple> = { ... }
// obj => { tesla: 'tesla'; 'model 3': 'model 3'; 'model X': 'model X'; 'model Y': 'model Y'}>

四、拿到数组第一个类型

// 实现1,解构推断首个索引类型
type First<T extends any[]> = T extends [infer P, ...infer R] ? P : never;
// 实现2,判断T本身是否空数组
type First<T extends any[]> = T['length'] extends 0 ? never : T[0];
type First<T extends any[]> = T extends [] ? never : T[0];
type First<T extends any[]> = T[number] extends never ? never : T[0];
type First<T extends any[]> = T extends never[] ? never : T[0];
// etc...只要满足T是空数组的表达式,都正确

First<[3,2,1]> // => 3
First<[]> // => never
First<[() => 123]> // () => 123

五、获取元组长度

const tesla = ['tesla', 'model 3', 'model X', 'model Y'] as const

// 实现
type Length<T extends any> =  T extends { length: infer L } ? L : never;
Length<typeof tesla> // => 4
TinyScript commented 2 years ago

六、Exclude 排除

Exclude<"a" | "b" | "c", "a"> // => "b" | "c";

// 实现,如果T被U约束,返回never,否则为T
type MyExclude<T, U> = T extends U ? never : T;;
MyExclude<"a" | "b" | "c", "a"> // => "b" | "c";

七、Awaited 获取Promise返回类型

type X = Promise<string>;
type Y = Promise<{ field: number }>;
type Z = Promise<string | number>;

// 实现1,易读版本
type Helper<P> = P extends Promise<any> ? Helper<MyAwaited<P>> : P;
type MyAwaited<P> = P extends Promise<infer V> ? Helper<V> : P;

// 实现2,正式版本,递归
type MyAwaited<P> = P extends Promise<infer V> ? MyAwaited<V> : P; 

MyAwaited<X>; // => string
MyAwaited<Y>; // => { field: number }
MyAwaited<Z>; // => string | number

八、If

// 实现
type If<C, T, F> = C extends true ? T : F;
type A = If<true, 'a', 'b'>  // => a
type B = If<false, 'a', 'b'> // => b

九、Concat 数组内类型合并

// 期望
Concat<[1, 2], [3, 4]> // => [1, 2, 3, 4]

// 实现
type Concat<T extends any[], U extends any[]> = T extends [... infer VT]
                                              ? U extends [...infer VU]
                                                ? [...VT, ...VU]
                                                : never
                                              : never
Concat<[], []> // => []
Concat<[], [1]> // => [1]
Concat<[1, 2], [3, 4]> // => [1, 2, 3, 4]
Concat<['1', 2, '3'], [false, boolean, '4']> // => ['1', 2, '3', false, boolean, '4']

十、Includes 包含

// 期望
Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>; // => true
Includes<['Kars', 'Esidisi','Wamuu', 'Santana'], 'Dio'>;    // =>false

// 实现
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
type Includes<T extends readonly any[], U> = T extends [infer F, ...infer R]
                                           ? Equal<F, U> extends true 
                                             ? true 
                                             : Includes<R, U>
                                           : false

Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'> // => true
Includes<['Kars', 'Esidisi','Wamuu', 'Santana'], 'Dio'> // => false
Includes<[1, 2, 3, 5, 6, 7], 7> // => true
Includes<[1, 2, 3, 5, 6, 7], 4> // => false
Includes<[1, 2, 3], 2> // => true
Includes<[1, 2, 3], 1> // => true
Includes<[{}], { a: 'A' }> // => false
Includes<[boolean, 2, 3, 5, 6, 7], false> // => false
Includes<[true, 2, 3, 5, 6, 7], boolean> // => false
Includes<[false, 2, 3, 5, 6, 7], false> // => true
Includes<[{ a: 'A' }], { readonly a: 'A' }> // => false
Includes<[{ readonly a: 'A' }], { a: 'A' }> // => false

十一、Push

// 期望
Push<[1, 2], '3'> // => [1, 2, '3']

// 实现
type Push<T extends any[], U> = [...T, U]

Push<[], 1> // => [1]
Push<[1, 2], '3'> // => [1, 2, '3']
Push<['1', 2, '3'], boolean> // => ['1', 2, '3', boolean]

十二、Unshift

// 期望
Unshift<[1, 2], 0> // => [0, 1, 2]

// 实现
type Unshift<T extends any[], U> = [U, ...T]

十三、实现函数的参数反射

const foo = (arg1: string, arg2: number, arg3: boolean): void => {} // => [string, number]
const bar = (arg1: boolean, arg2: {a: 'A'}): void => {} // => [boolean, {a: 'A'}]
const baz = (): void => {} // => []

// 实现
type MyParamters<T extends (...args: any[]) => any> = T extends (...args: [...infer P]) => any ? P : never;

MyParamters<typeof foo> // => [string, number]
MyParamters<typeof bar> // => [boolean, {a: 'A'}]
MyParamters<typeof baz> // => []
TinyScript commented 2 years ago

十四、获取返回类型

const fn = (v: boolean) => {
  if (v)
    return 1
  else
    return 2
}

// 期望
ReturnType<typeof fn> // => "1 | 2"

// 实现
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type ComplexObject = {
  a: [12, 'foo']
  bar: 'hello'
  prev(): number
}

const fn = (v: boolean) => v ? 1 : 2
const fn1 = (v: boolean, w: any) => v ? 1 : 2

MyReturnType<() => string> // => string
MyReturnType<() => 123> // => 123
MyReturnType<() => ComplexObject> // => ComplexObject
MyReturnType<() => Promise<boolean>> // => Promise<boolean>
MyReturnType<() => () => 'foo'> // => () => 'foo'
MyReturnType<typeof fn> // => 1 | 2
MyReturnType<typeof fn1> // => 1 | 2
TinyScript commented 2 years ago

十五、Omit 忽略

interface Todo {
  title: string
  description: string
  completed: boolean
}

// 期望
Omit<Todo, 'description'> // => { title: string, completed: boolean }

//  实现
type Exclude<T, K> = T extends K ? never : T;
type MyOmit<T, K> = { [k in Exclude<keyof T, K>]: T[k] }

MyOmit<Todo, 'description'> // => { title: string, completed: boolean }
MyOmit<Todo, 'description' | 'completed'> // => { title: string }

十六、可控的Readonly

interface Todo1 {
  title: string
  description?: string
  completed: boolean
}

interface Todo2 {
  readonly title: string
  description?: string
  completed: boolean
}

interface Expected {
  readonly title: string
  readonly description?: string
  completed: boolean
}

// 期望
Readonly<Todo1, 'title' | 'description'> 
/** => Expected
 * { 
 *   readonly title: string, 
 *   readonly description?: string, 
 *   completed: boolean
 * }
 */

// 实现,思路:先选出需要readonly的key,再通过Omit过滤掉readonly的key,取并集
type Exclude<T, K> = T extends K ? never : T;
type Omit<T, K> = { [k in Exclude<keyof T, K>]: T[k] };
type MyReadonly<T, K extends keyof T = keyof T> = { readonly [k in K]: T[k] } & Omit<T, K>;

MyReadonly2<Todo1>; // => Readonly<Todo1>
MyReadonly2<Todo1,  'title' | 'description'>; // => Expected
MyReadonly2<Todo2, 'title' | 'description'>; // => Expected
TinyScript commented 2 years ago

十六、Deep Readonly

// 预定义的数据:
type X = {
  a: () => 22
  b: string
  c: {
    d: boolean
    e: {
      g: {
        h: {
          i: true
          j: 'string'
        }
        k: 'hello'
      }
    }
  }
}

type Expected = {
  readonly a: () => 22
  readonly b: string
  readonly c: {
    readonly d: boolean
    readonly e: {
      readonly g: {
        readonly h: {
          readonly i: true
          readonly j: 'string'
        }
        readonly k: 'hello'
      }
    }
  }
}

// 期望
DeepReadonly<X> // => 与Expected相等

// 实现,除了函数不能被readonly,其他的类型全部递归readonly处理
type DeepReadonly<T> = { readonly [k in keyof T]: T[k] extends Function ? T[k] : DeepReadonly<T[k]> }

DeepReadonly<X> // => Expected

十七、TupleToUnion 元组转并集类型

// 期望
TupleToUnion<[123, '456', true]> // => 123 | '456' | true

// 实现1,通过推断T数组类型拿到并集
type TupleToUnion<T> = T extends (infer R)[] ? R : never;

// 实现2,通过不断递归Rest的值,做到First | ...Rest的每次递归的并集累计效果
type TupleToUnion<T> = T extends [infer F, ...infer R] ? F | TupleToUnion<R> : never;

TupleToUnion<[123, '456', true]> // => 123 | '456' | true
TupleToUnion<[123]> // => 123
TinyScript commented 2 years ago

十八、Chainable Options 链式调用

declare const config: Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

// 期望得到:
interface Result {
  foo: number
  name: string
  bar: {
    value: string
  }
}

// 实现
type Chainable<T = {}> = {
  option<K extends string, V>(key: K, value: V): Chainable<T & { [k in K]: V}>
  get(): T
}
// 每次调用option都将key与value和T取并集

十九、Last of Array

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

// 期望
type tail1 = Last<arr1> // => 'c'
type tail2 = Last<arr2> // => 1

// 实现
type Last<T extends any[]> = T extends [...infer R, infer L] ? L : never;

二十、Pop

type arr1 = ['a', 'b', 'c', 'd']
type arr2 = [3, 2, 1]

// 期望
type re1 = Pop<arr1> // => ['a', 'b', 'c']
type re2 = Pop<arr2> // => [3, 2]

// 实现
type Pop<T extends any[]> = T extends [...infer R, infer L] ? R : never;
TinyScript commented 2 years ago

二十一、Promise.all

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

// 期望:
Promise.all([promise1, promise2, promise3] as const) // => Promise<[number, 42, string]>
/**
 * 核心思路1:只要是[] as const,即为Readonly<[]>
 * 核心思路2:Promise的泛型如果接收一个对象,对象key可作为数组的索引
 * 例:
 * Promise<['a', 'b', 'c']> => Promise.resolve(['a', 'b', 'c'])
 * Promise<{ 0: 'a', 1: 'b', 2: 'c'}> => Promise.resolve(['a', 'b', 'c'])
 */

// 实现:
declare function PromiseAll<T extends unknow[]>(values: Readonly<[...T]>): Promise<
  { 
    [k in keyof T]: T[k] extends Promise<infer V> ? V : T[k]
  }
>

/**
 * 简单解释:根据核心思路2,PromiseAll会返回一个Promise类型,泛型为数组类型,
 * 并判断的值是否为Promise:
 * - 若是true,拿到Promise的推断类型V,返回推断出来的V类型。
 * - 若是false,说明为const,返回当前值
 */

二十二、Type Lookup 类型查阅

interface Cat {
  type: 'cat'
  breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}

interface Dog {
  type: 'dog'
  breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
  color: 'brown' | 'white' | 'black'
}

type Animal = Cat | Dog

// 期望:
LookUp<Animal, 'dog'> // => Dog
LookUp<Animal, 'cat'> // => Cat

// 实现
type LookUp<U, T> = U extends { type: infer V } 
                  ? V extends T
                    ? U
                    : never
                  : never 
TinyScript commented 2 years ago

二十三、 TrimLeft 修剪左侧空字符

// 期望
type trimed = TrimLeft<'  Hello World  '> // => "Hello World"

// 实现
type Ident = ' ' | '\t' | '\n'
type TrimLeft<T extends string> = T extends `${Ident}${infer R}` ? TrimLeft<R> : T;