Typescript类型体操

ts类型体操

Column: March 30, 2022 10:36 AM
Status: Yes
Tags: ts

Easy

  • 实现Pick

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /*
    **从类型 T 中选择出属性 K,构造成一个新的类型,**
    */

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

    //========= 用例 =========
    interface Todo {
    title: string
    description: string
    completed: boolean
    }

    type TodoPreview = MyPick<Todo, 'title' | 'completed'>

    const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
    }
  • 实现 Readonly

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /*
    该 Readonly 会接收一个 泛型参数,并返回一个完全一样的类型,只是所有属性都会被 readonly 所修饰。
    也就是不可以再对该对象的属性赋值。
    */
    //========= 实现 =========
    type MyReadonly<T> = {
    readonly [key in keyof T]: T[key]
    }

    //========= 用例 =========
    interface Todo {
    title: string
    description: string
    }

    const todo: MyReadonly<Todo> = {
    title: "Hey",
    description: "foobar"
    }

    todo.title = "Hello" // Error: cannot reassign a readonly property
    todo.description = "barFoo" // Error: cannot reassign a readonly property
  • 元组转换为对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    传入一个元组类型,将这个元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来。

    // T extends readonly string[], 泛型约束,T 类型限定在 字符串类型的数组
    // P in T[number], P 是 数组值的集合,作为 Key,同时也作为值返回
    type TupleToObject<T extends readonly string[]> = {
    [P in T[number]]: P
    }

    //========= 用例 =========
    const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

    type result = TupleToObject<typeof tuple>
    // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
  • 第一个元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    实现一个通用First<T>,它接受一个数组T并返回它的第一个元素的类型

    // infer 类型占位,定一个元素的类型,具体类型需要在运行时确定
    type First<T extends any[]> = T extends [infer R, ...unknown[]] ? R : never

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

    type head1 = First<arr1> // expected to be 'a'
    type head2 = First<arr2> // expected to be 3
  • 获取元组长度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    创建一个通用的Length,接受一个readonly的数组,返回这个数组的长度。

    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
  • 实现 Excule

    1
    2
    3
    4
    5
    实现内置的Exclude <T,U>类型,但不能直接使用它本身。从联合类型T中排除U的类型成员,来构造一个新的类型。

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

    type demo1 = Exclude<string | number | (() => void), string> // number | () => void
  • Awaited

    1
    2
    3
    4
    5
    6
    假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,
    我们用 Promise 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。

    type MyAwaited<T extends Promise<unknown>> = T extends Promise<infer Res> ? Res extends Promise<unknown> ? MyAwaited<Res> : Res : T;
    // 或者
    type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T;
  • If

    1
    2
    3
    4
    5
    6
    7
    实现一个 IF 类型,它接收一个条件类型 C ,一个判断为真时的返回类型 T ,以及一个判断为假时的返回类型 F。 
    C 只能是 true 或者 false, T 和 F 可以是任意类型。

    type If<C extends boolean, T, F> = C extends true ? T : F

    type A = If<true, 'a', 'b'> // expected to be 'a'
    type B = If<false, 'a', 'b'> // expected to be 'b'
  • Concat

    1
    2
    3
    4
    5
    在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。

    type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]

    type Result = Concat<[1], [2]> // expected to be [1, 2]
  • Includes

    1
    2
    3
    4
    5
    6
    7
    在类型系统里实现 JavaScript 的 Array.includes 方法,这个类型接受两个参数,返回的类型要么是 true 要么是 false。

    // Equal<U, First> extends true ? 判断 U 和 First是否是相同类型, 如果不是数组移位继续判断
    // 用到了递归,遍历完数组还是没找到匹配类型的话,返回false
    type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ? Equal<U, First> extends true ? true : Includes<Rest, U> : false

    type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
  • Push

    1
    2
    3
    4
    5
    在类型系统里实现通用的 Array.push

    type Push<T extends unknown[], U> = [...T, U]

    type Result = Push<[1, 2], '3'> // [1, 2, '3']
  • Unshit

    1
    2
    3
    4
    5
    实现类型版本的 Array.unshift。

    type Unshift<T extends unknown[], U> = [U, ...T]

    type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
  • Parameters

    1
    2
    3
    实现内置的 Parameters 类型,获取函数的参数类型

    type MyParameters<T extends (...args: any[]) => any> = T extends ((...args: infer R) => any) ? R : never

Middle

  • 获取函数返回类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    不使用 ReturnType 实现 TypeScript 的 ReturnType<T> 泛型。

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

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

    type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"
  • 实现 Omit

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    不使用 Omit 实现 TypeScript 的 Omit<T, K> 泛型, Omit 会创建一个省略 K 中字段的 T 对象。

    // K extends keyof T, 从T中取所有key的合集
    // Exclude<keyof T, K>, 从T的key的合集中,剔除K
    type MyOmit<T, K extends keyof T> = {
    [p in Exclude<keyof T, K>]: T[p]
    }

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

    type TodoPreview = MyOmit<Todo, 'description' | 'title'>

    const todo: TodoPreview = {
    completed: false,
    }
  • Readonly 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    实现一个通用MyReadonly2<T, K>,它带有两种类型的参数T和K。
    K指定应设置为Readonly的T的属性集。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly<T>一样。

    type MyReadonly2<T, K extends keyof T = keyof T> = Omit<T, K> & Readonly<Pick<T, K>>

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

    const todo: MyReadonly2<Todo, 'title' | 'description'> = {
    title: "Hey",
    description: "foobar",
    completed: false,
    }

    todo.title = "Hello" // Error: cannot reassign a readonly property
    todo.description = "barFoo" // Error: cannot reassign a readonly property
    todo.completed = true // OK
  • 深度 Readonly

    简单来说,TypeScript中的Record可以实现定义一个对象的 keyvalue 类型,Record 后面的泛型就是对象键和值的类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    interface CatInfo {
    age: number;
    breed: string;
    }

    type CatName = "mincat" | "licat" | "mordred";

    const cats: Record<CatName, CatInfo> = {
    mincat: { age: 10, breed: "小猫er" },
    licat: { age: 5, breed: "李猫er" },
    mordred: { age: 16, breed: "无名猫er" },
    };
</aside>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
实现一个通用的DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。
您可以假设在此挑战中我们仅处理对象。数组,函数,类等都无需考虑。

// 利用类型递归, Record<string, unkonwn> 判断仍然是对象类型
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends Record<string, unknown> ? 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`
  • 元组转合集

    1
    2
    3
    4
    5
    6
    7
    8
    实现泛型TupleToUnion<T>,它返回元组所有值的合集。

    // T[number] 获取元组类型的值
    type TupleToUnion<T extends readonly unknown[]> = T[number]

    type Arr = ['1', '2', '3']

    type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
  • 可串联构造器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    在 JavaScript 中我们很常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给他附上类型吗?

    在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 option(key, value)
    和 get()。在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果。

    type Chainable<T = {}> = {
    option<K extends string, V = unknown>(key: K, value: V): Chainable<T & Record<K, V>> // option 是函数类型
    get(): T
    }

    declare const config: Chainable

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

    // 期望 result 的类型是:
    interface Result {
    foo: number
    name: string
    bar: {
    value: string
    }
    }
  • 最后一个元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    实现一个通用Last<T>,它接受一个数组T并返回其最后一个元素的类型。

    type Last<T extends any[]> = T extends [...any[], infer Last] ? Last : never

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

    type tail1 = Last<arr1> // expected to be 'c'
    type tail2 = Last<arr2> // expected to be 1
  • 出堆

    1
    2
    3
    4
    5
    6
    7
    8
    9
    实现一个通用Pop<T>,它接受一个数组T并返回一个没有最后一个元素的数组。

    type Pop<T extends any[]> = T extends [...infer First, any] ? First : never

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

    type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
    type re2 = Pop<arr2> // expected to be [3, 2]
  • Promise.all

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    键入函数PromiseAll,它接受PromiseLike对象数组,返回值应为Promise<T>,其中T是解析的结果数组

    declare function PromiseAll<T extends any[]>(values: readonly [...T]) : Promise<{
    [R in keyof T]: T[R] extends Promise<infer S> ? S : T[R]
    }>

    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 = Promise.all([promise1, promise2, promise3] as const)
  • Type Lookup

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    在此挑战中,我们想通过在联合Cat | Dog中搜索公共type字段来获取相应的类型。换句话说,在以下示例中,
    我们期望LookUp<Dog | Cat, 'dog'>获得Dog,LookUp<Dog | Cat, 'cat'>获得Cat。

    type LookUp<U, T> = U extends {type: T} ? U : 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, 'dog'> // expected to be `Dog`
  • Trim Left

    1
    2
    3
    4
    5
    实现 TrimLeft<T> ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。

    type TrimLeft<S extends string> = S extends `${' ' | '\n' | '\t'}${infer R}` ? TrimLeft<R> : S

    type trimed = TrimLeft<' Hello World '> // 应推导出 'Hello World '
  • Trim

    1
    2
    3
    4
    5
    6
    7
    Implement Trim<T> which takes an exact string type and returns a new string with the whitespace 
    from both ends removed.
    删除两端的空格

    type Trim<S extends string> = S extends `${' ' | '\n' | '\t'}${infer L}` ? Trim<L> : S extends `${infer R}${' ' | '\n' | '\t'}` ? Trim<R> : S

    type trimed = Trim<' Hello World '> // expected to be 'Hello World'
  • Capitalize

    1
    2
    3
    4
    5
    首字母大写,其他部分保持原样
    // Uppercase, 将字符串转为大写
    type MyCapitalize<S extends string> = S extends `${infer R}${infer Rest}` ? `${Uppercase<R>}${Rest}` : ''

    type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'
  • Replace

    1
    2
    3
    4
    5
    6
    实现 Replace<S, From, To> 将字符串 S 中的第一个子字符串 From 替换为 To 。

    type Replace<S extends string, From extends string, To extends string> =
    From extends '' ? S : S extends `${infer Pre}${From}${infer Last}` ? `${Pre}${To}${Last}` : S

    type replaced = Replace<'types are fun!', 'fun', 'awesome'> // 期望是 'types are awesome!'
  • ReplaceAll

    1
    2
    3
    4
    5
    实现 ReplaceAll<S, From, To> 将一个字符串 S 中的所有子字符串 From 替换为 To。

    type ReplaceAll<S extends string, From extends string, To extends string> = From extends '' ? S : S extends `${infer Pre}${From}${infer Last}` ? `${Pre}${To}${ReplaceAll<Last, From, To>}` : S

    type replaced = ReplaceAll<'t y p e s', ' ', ''> // 期望是 'types'
  • 追加参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    实现一个泛型 AppendArgument<Fn, A>,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。
    G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。

    type AppendArgument<Fn, A> = Fn extends (...args: infer P) => infer Res ? (...args: [...P, A]) => Res : never

    type Fn = (a: number, b: string) => number

    type Result = AppendArgument<Fn, boolean>
    // 期望是 (a: number, b: string, x: boolean) => number
  • Permutation(pas)

    1
    2
    3
    4
    5
    实现将联合类型转换为包含联合排列的数组的排列类型。

    type Permutation<T, U = T> = [T] extends [never] ? [] : (T extends U ? [T, ...Permutation<Exclude<U, T>>] : [])

    type perm = Permutation<'A' | 'B' | 'C'>; // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
  • Length of String

    1
    2
    3
    4
    5
    计算字符串文字的长度

    // 先把字符串转换成数组,再取length属性
    type StrToArr<S extends string> = S extends `${infer F}${infer S}` ? [F, ...StrToArr<S>] : [];
    type LengthOfString<S extends string> = S extends string ? StrToArr<S>["length"] : 0
  • Flatten

    1
    2
    3
    数组展平

    type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, 5]