好了, 接下来到中等的挑战了, 拭目以待。

Get Return Type

  • 不使用 ReturnType 实现 TypeScript 的 ReturnType 泛型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type MyReturnType<T> = 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

  • 不使用Omit实现TypeScript的Omit<T, K> 泛型。Omit会创建一个省略 K 中字段的 T 对象。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    type MyOmit<T, K extends keyof T> = { [key in keyof T as key extends K ? never : key]: T[key] }
    // as key extends K ? never : key 这部分叫做重映射
    interface Todo {
    title: string
    description: string
    completed: boolean
    }

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

    const todo: TodoPreview = {
    completed: false,
    }

Readonly 2

  • 实现一个通用MyReadonly2<T, K>,它带有两种类型的参数T和K。K指定应设置为Readonly的T的属性集。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly一样。
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    // = keyof T作为默认值
    type MyReadonly2<T extends Record<string, any>, K extends keyof T = keyof T> = {
    readonly[key in K]: T[key]
    } & {
    [ key in keyof T as key extends K ? never : key ]: T[key]
    }

    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

    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
    }
    Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>
    Expect<Alike<MyReadonly2<Todo1, 'title' | 'description'>, Expected>>,
    Expect<Alike<MyReadonly2<Todo2, 'title' | 'description'>, Expected>>
    Expect<Alike<MyReadonly2<Todo2, 'description' >, Expected>>

Deep Readonly

  • 实现一个通用的DeepReadonly,它将对象的每个参数及其子对象递归地设为只读。
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    type DeepReadonly<T> = { readonly [ key in keyof T ]: T[key] extends Record<string, any> ?(T[key] extends Function ? T[key] : DeepReadonly<T[key]>) : T[key] }
    // 把Function情况也考虑进去了
    type X1 = {
    a: () => 22
    b: string
    c: {
    d: boolean
    e: {
    g: {
    h: {
    i: true
    j: 'string'
    }
    k: 'hello'
    }
    l: [
    'hi',
    {
    m: ['hey']
    },
    ]
    }
    }
    }

    type X2 = { a: string } | { b: number }

    type cases = [
    Expect<Equal<DeepReadonly<X1>, Expected1>>,
    Expect<Equal<DeepReadonly<X2>, Expected2>>,
    ]
    type Expected1 = {
    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'
    }
    readonly l: readonly [
    'hi',
    {
    readonly m: readonly ['hey']
    },
    ]
    }
    }
    }

    type Expected2 = { readonly a: string } | { readonly b: number }

Tuple to Union

  • 实现泛型TupleToUnion,它返回元组所有值的合集。
    1
    2
    3
    4
    5
    6
    type TupleToUnion<T extends any[]> = T extends Array<infer U> ? U : never
    type TupleToUnion<T extends any[]> = T[number]

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

    type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'

Chainable Options

  • 在 JavaScript 中我们经常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给它赋上类型吗?
    在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 option(key, value) 和 get()。在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果。
  • 你只需要在类型层面实现这个功能 - 不需要实现任何 TS/JS 的实际逻辑。
    你可以假设 key 只接受字符串而 value 接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 key 只会被使用一次。
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    type Chainable<T = {}> = {
    option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<Omit<T, K> & {[P in K]: V}>;
    get(): T
    }
    /**
    * 1. Chainable类型添加一个新的类型参数T,并且不能忘记默认它是一个空对象
    * 2. 我们希望option(key, value)返回Chainable类型本身(我们希望有可能进行链式调用),但是要将类型信息累加到其类型参数
    * 3. 当有相同K时,后面覆盖旧的
    */

    declare const a: Chainable

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

    const result2 = a
    .option('name', 'another name')
    // @ts-expect-error
    .option('name', 'last name')
    .get()

    const result3 = a
    .option('name', 'another name')
    // @ts-expect-error
    .option('name', 123)
    .get()

    type cases = [
    Expect<Alike<typeof result1, Expected1>>,
    Expect<Alike<typeof result2, Expected2>>,
    Expect<Alike<typeof result3, Expected3>>,
    ]

    type Expected1 = {
    foo: number
    bar: {
    value: string
    }
    name: string
    }

    type Expected2 = {
    name: string
    }

    type Expected3 = {
    name: number
    }

Last of Array

  • 实现一个通用Last,它接受一个数组T并返回其最后一个元素的类型。
    1
    2
    3
    4
    5
    6
    7
    type Last<T extends unknown[]> = T extends [...infer First, 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

Pop

  • 实现一个通用Pop,它接受一个数组T,并返回一个由数组T的前length-1项以相同的顺序组成的数组。
    1
    2
    3
    4
    5
    6
    7
    type Pop<T extends unknown[]> = T extends [...infer First, infer Last] ? [... First] : []

    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]

Primise.all

  • 键入函数PromiseAll,它接受PromiseLike对象数组,返回值应为Promise,其中T是解析的结果数组。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    type MyAwaited<T> = T extends PromiseLike<infer R> ? R : T
    declare function PromiseAll<T extends unknown[]>(values: readonly [...T]): Promise<{
    [key in keyof T]: MyAwaited<T[key]>
    }>

    const promiseAllTest1 = PromiseAll([1, 2, 3] as const)
    const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const)
    const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])
    const promiseAllTest4 = PromiseAll<Array<number | Promise<number>>>([1, 2, 3])

    type cases = [
    Expect<Equal<typeof promiseAllTest1, Promise<[1, 2, 3]>>>,
    Expect<Equal<typeof promiseAllTest2, Promise<[1, 2, number]>>>,
    Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>,
    Expect<Equal<typeof promiseAllTest4, Promise<number[]>>>,
    ]

Type Lookup

  • 有时,您可能希望根据某个属性在联合类型中查找类型。
  • 在此挑战中,我们想通过在联合类型Cat | Dog中搜索公共type字段来获取相应的类型。换句话说,在以下示例中,我们期望LookUp<Dog | Cat, ‘dog’>获得Dog,LookUp<Dog | Cat, ‘cat’>获得Cat。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    type LookUp<U extends {type: string}, T extends string> =  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`