最近在看TypeScript类型, 发现远不是我想的只是基于JavaScript上加点类型注释。

后来在github上发现了TypeScript type challenges(我愿称之为极其变态的挑战)

对目前的我来说, 还是有很大的难度… 特此记录一下简单篇的类型训练!
type-challenges

Pick

  • 实现 TS 内置的 Pick<T, K>,但不可以使用它。从类型 T 中选择出属性 K,构造成一个新的类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    type MyPick<T, K extends keyof T> = {
    [P in K]: T[P]
    }
    interface Todo {
    title: string
    description: string
    completed: boolean
    }

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

    const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
    }

Readonly

  • 实现Readonly,接收一个泛型参数,并返回一个完全一样的类型,只是所有属性都会被readonly所修饰。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    type MyReadonly<T> = {
    readonly [key in keyof T]: T[key]
    }

    interface Todo {
    title: string;
    description: string;
    }

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

    console.log(todoObj.title)
    todoObj.description = "barFoo"; // Error: cannot reassign a readonly property

Tuple to Object

  • 实现TupleToObject,传入元组类型,将元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来。
    1
    2
    3
    4
    5
    6
    7
    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>; // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

First of Array

  • 实现First,他接受一个数组 T 并返回它的第一个元素类型
    1
    2
    3
    4
    5
    6
    7
    type First<T extends any[]> = T extends [] ? never : T[0];

    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

Length of Tuple

  • 创建一个通用的Length,接受一个readonly的数组,返回这个数组的长度。
    1
    2
    3
    4
    5
    6
    7
    type Length<T extends readonly any[]> = 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

Exclude

  • 实现内置的Exclude <T, U>类型,但不能直接使用它本身。
    1
    2
    3
    type MyExclude<T, U> = T extends U ? never : T

    type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'

Awaited

  • 假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    type MyAwaited<T> = T extends PromiseLike<infer R> ? MyAwaited<R> : T

    type X = Promise<string>
    type Y = Promise<{ field: number }>
    type Z = Promise<Promise<string | number>>
    type Z1 = Promise<Promise<Promise<string | boolean>>>
    type T = { then: (onfulfilled: (arg: number) => any) => any }

    type Result = MyAwaited<X> // string
    type Result = MyAwaited<Y> // { field: number }
    type Result = MyAwaited<Z> // string | number
    type Result = MyAwaited<Z1> // string | boolean
    type Result = MyAwaited<T> // number

If

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

  • 在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。
    1
    2
    3
    type Concat<T extends any[], U extends any[]> = [...T, ...U]

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

Includes

  • 在类型系统里实现 JavaScript 的 Array.includes 方法,这个类型接受两个参数,返回的类型要么是 true 要么是 false。
    1
    2
    3
    type Includes<T extends readonly any[], U> =  U extends T[number] ? true : false

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

Push

  • 在类型系统里实现通用的 Array.push 。
    1
    2
    3
    type Push<T extends any[], U> = [...T, U]

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

Unshift

  • 实现类型版本的 Array.unshift。
    1
    2
    3
    type Unshift<T extends any[], U> = [U, ...T]

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

Parameters

  • 实现内置的 Parameters 类型
    1
    2
    3
    4
    5
    type MyParameters<T extends (...args: any[]) => any> =  T extends (...args: infer R) => any ? R : never;

    const foo = (arg1: string, arg2: number): void => {}

    type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]