美文网首页
TypeScript 类型挑战 Medium

TypeScript 类型挑战 Medium

作者: binyu1231 | 来源:发表于2022-07-04 17:54 被阅读0次

    TypeScript 类型挑战 Medium

    [[toc]]

    • 项目地址 Github
    • 项目描述: 高质量的类型可以帮助提高项目的可维护性,同时避免潜在的错误。

    Medium 版需要注意的事情

    • 这部分挑战有很多使用递归来实现。还涉及很多类型语言的特殊用法。比如如何判断 never。利用数组来计数等等。

    Get Return Type

    Medium, #infer, #built-in

    实现 TS 内置的 ReturnType<T>,但不可以使用它。

    const fn = (v: boolean) => {
      if (v)
        return 1
      else
        return 2
    }
    
    type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"
    

    答案

    type MyReturnType<T> = T extends (...arg: any) => infer R ? R : never
    

    Omit

    Medium, #union, #built-in

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

    interface Todo {
      title: string
      description: string
      completed: boolean
    }
    
    type TodoPreview = MyOmit<Todo, 'description' | 'title'>
    
    const todo: TodoPreview = {
      completed: false,
    }
    
    

    答案

    type MyExclude<T, K> = T extends K ? never : T
    
    type MyOmit<T, K> = {
      [V in MyExclude<keyof T, K>]: T[V]
    }
    

    Readonly 2

    Medium, #readonly, #object-keys

    实现一个通用MyReadonly2<T, K>,它带有两种类型的参数TK

    K指定应设置为Readonly的T的属性集。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly<T>一样。

    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
    
    

    答案

    
    type MyReadonly2<T, K extends keyof T = keyof T> = {
    
      [P in keyof T as P extends K ? never : P] : T[P]
      
    } & {
    
      readonly [P in keyof T as P extends K ? P : never] : T[P]
      
    }
    

    Deep Readonly

    Medium, #readonly, #object-keys, #deep

    实现一个通用的DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。

    您可以假设在此挑战中我们仅处理对象。数组,函数,类等都无需考虑。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。

    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`
    
    

    答案

    type DeepReadonly<T> = {
    
      readonly [K in keyof T]: keyof T[K] extends never ? T[K] : DeepReadonly<T[K]>
    
    }
    

    Tuple to Union

    Medium, #infer, #tuple, #union

    实现泛型TupleToUnion<T>,它返回元组所有值的合集。

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

    答案

    
    type TupleToUnion<T extends any[]> = T[number]
    
    

    Chainable Options

    Medium, #application

    在 JavaScript 中我们很常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给他附上类型吗?

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

    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
      }
    }
    

    你只需要在类型层面实现这个功能 - 不需要实现任何 TS/JS 的实际逻辑。

    你可以假设 key 只接受字符串而 value 接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 key 只会被使用一次。

    答案

    type Chainable<T = {}> = {
    
      option<K extends string, V extends any>(key: K, value: V)
        : Chainable<T & { [P in K] : V }>
      get(): T
    
    }
    

    Last of Array

    Medium, #array

    实现一个通用Last<T>,它接受一个数组T并返回其最后一个元素的类型。

    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
    

    答案

    type Last<T extends any[]> = T extends [... infer rest, infer L] ? L : undefined
    

    Pop, Shift, Push, Unshift

    Medium, #array

    实现一个通用Pop<T>,它接受一个数组T并返回一个没有最后一个元素的数组。

    
    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]
    
    

    额外:同样,您也可以实现ShiftPushUnshift吗?

    答案

    
    type Pop<T extends any[]> = T extends [... infer R, infer last] ? R : T
    
    type Push<T extends any[], V extends any> = [...T, V]
    
    type Shift<T extends any[]> = T extends [infer head, ... infer R] ? R : T
    
    type Unshift<T extends any[], V extends any> = [V, ...T]
    
    

    Promise.all

    Medium, #array, #built-in

    键入函数PromiseAll,它接受PromiseLike对象数组,返回值应为Promise<T>,其中T是解析的结果数组。

    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)
    
    

    答案

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

    Type Lookup

    Medium, #union, `#map

    有时,您可能希望根据某个属性在联合类型中查找类型。

    在此挑战中,我们想通过在联合类型Cat | Dog中搜索公共type字段来获取相应的类型。换句话说,在以下示例中,我们期望LookUp<Dog | Cat, 'dog'>获得DogLookUp<Dog | Cat, 'cat'>获得Cat

    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`
    
    

    答案

    type LookUp<U, V extends string> = U extends { type: infer T } 
        ? V extends T ? U : never 
        : never
    
    

    Trim Left

    Medium, #template-literal

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

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

    答案

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

    Trim

    Medium, #template-literal

    实现Trim<T>,它是一个字符串类型,并返回一个新字符串,其中两端的空白符都已被删除。

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

    答案

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

    Capitalize

    Medium, #template-literal

    实现 Capitalize<T> 它将字符串的第一个字母转换为大写,其余字母保持原样。

    type capitalized = MyCapitalize<'hello world'> // expected to be 'Hello world'
    

    答案

    type MyCapitalize<S extends string> = S extends `${infer H}${infer R}` 
        ? `${Uppercase<H>}${R}` : S
    
    

    Replace

    Medium, #template-iteral

    实现 Replace<S, From, To> 将字符串 S 中的第一个子字符串 From 替换为 To

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

    答案

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

    ReplaceAll

    Medium, #template-literal

    实现 ReplaceAll<S, From, To> 将一个字符串 S 中的所有子字符串 From 替换为 To

    type replaced = ReplaceAll<'t y p e s', ' ', ''> // 期望是 'types'
    
    

    答案

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

    Append Argument

    Medium, #arguments

    实现一个泛型 AppendArgument<Fn, A>,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 GG 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。

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

    答案

    type AppendArgument<Fn extends (...args: any[]) => any, A> = 
        Fn extends (...args: infer Arg) => infer R 
        ? (...arg: [...Arg, A]) => R 
        : never
    

    Permutation

    Medium, #union

    实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。

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

    答案

    type Permutation<T, K=T> =
        [T] extends [never]
            ? []
            : K extends K
                ? [K, ...Permutation<Exclude<T, K>>]
                : never
    

    https://github.com/type-challenges/type-challenges/issues/614

    Note

    T extends never // 不生效
    T[] extends never // 不生效
    [T] extends [never] // 生效
    

    Length of String

    Medium, #template-literal

    计算字符串的长度,类似于 String#length

    LengthOfString<'kumiko'> // 6
    

    答案

    type StringToArray<S extends string> = 
        S extends `${infer H}${infer R}` 
            ? [H, ...StringToArray<R>] 
            : []
    
    type LengthOfString<S extends string> = StringToArray<S>['length']
    

    Flatten

    Medium, #array

    在这个挑战中,你需要写一个接受数组的类型,并且返回扁平化的数组类型。

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

    答案

    type Flatten<T extends any[]> = 
        T extends [infer H, ...infer R]
          ? [
            ...(H extends any[] ? Flatten<H> : [H]),
            ...(R extends any[] ? Flatten<R> : [R]),
          ]
          : []
    

    Append to object

    Medium, #object-keys

    实现一个为接口添加一个新字段的类型。该类型接收三个参数,返回带有新字段的接口类型。

    type Test = { id: '1' }
    type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }
    

    答案

    type AppendToObject<
        T extends {}, 
        U extends string | number | symbol, 
        V
    > = {
        [K in keyof T | U]: K extends keyof T
            ? K extends U
                ? V
                : T[K]
            : V
    }
    

    Absolute

    Medium, #math ,#template-literal

    实现一个接收string,number或bigInt类型参数的Absolute类型,返回一个正数字符串。

    type Test = -100;
    type Result = Absolute<Test>; // expected to be "100"
    

    答案

    type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer X}` ? X : `${T}`
    

    String to Union

    Medium, #union , #string

    实现一个将接收到的String参数转换为一个字母Union的类型。

    type Test = '123';
    type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"
    

    答案

    type StringToUnion<T extends string> = 
        T extends `${infer H}${infer R}` 
            ? H | StringToUnion<R> 
            : never
    

    Merge

    Medium, #object

    实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。

    type foo = {
      name: string;
      age: string;
    }
    type coo = {
      age: number;
      sex: string
    }
    
    type Result = Merge<foo,coo>; // expected to be {name: string, age: number, sex: string}
    

    答案

    type Merge<T extends {}, S extends {}> = {
      [K in (keyof T | keyof S)]: K extends keyof S
        ? S[K]
        : K extends keyof T ? T[K] : never
    }
    

    KebabCase

    Medium, #

    FooBarBaz -> foo-bar-baz

    type KebabCase<'`FooBarBaz`'> // `foo-bar-baz`
    

    答案

    type _KebaCase<S extends string> = S extends `${infer H}${infer R}`
      ? H extends Lowercase<H>
        ? `${H}${_KebaCase<R>}`
        : `-${Lowercase<H>}${_KebaCase<R>}`
      : ''
    
      
    
    type KebabCase<S extends string> = S extends `${infer H}${infer R}`
      ? H extends Lowercase<H>
        ? `${H}${_KebaCase<R>}`
        : `${Lowercase<H>}${_KebaCase<R>}`
      : ''
    

    Diff

    Medium, #object

    获取两个接口类型中的差值属性。

    type Foo = {
      a: string;
      b: number;
    }
    type Bar = {
      a: string;
      c: boolean
    }
    
    type Result1 = Diff<Foo,Bar> // { b: number, c: boolean }
    type Result2 = Diff<Bar,Foo> // { b: number, c: boolean }
    

    答案

    type Diff<O extends {}, O1 extends {}> = {
      [K in Exclude<keyof O1, keyof O> | Exclude<keyof O, keyof O1>] :
        K extends keyof O
          ? O[K]
          : K extends keyof O1
            ? O1[K]
            : never
    }
    

    AnyOf

    Medium, #array

    在类型系统中实现类似于 Python 中 any 函数。类型接收一个数组,如果数组中任一个元素为真,则返回 true,否则返回 false。如果数组为空,返回 false

    type Sample1 = AnyOf<[1, '', false, [], {}]> // expected to be true.
    type Sample2 = AnyOf<[0, '', false, [], {}]> // expected to be false.
    

    答案

    type Falsy = '' | [] | false | Record<keyof any, never> | 0
    type AnyOf<T extends readonly any[]> = T[number] extends Falsy ? false : true
    

    IsNever

    Medium, #union , #utils

    实现 IsNever 类型, 解析输入 T 类型为 never 返回 true 否则 返回 false

    type A = IsNever<never>  // expected to be true
    type B = IsNever<undefined> // expected to be false
    type C = IsNever<null> // expected to be false
    type D = IsNever<[]> // expected to be false
    type E = IsNever<number> // expected to be false
    

    答案

    type IsNever<T extends any> = [T] extends [never] ? true : false
    
    // note T extends never 无法判定
    
    

    IsUnion

    Medium, #union , #utils

    实现 IsUnion 类型, 解析输入 T 类型为联合类型 返回 true 否则 返回 false

    type case1 = IsUnion<string>  // false
    type case2 = IsUnion<string|number>  // true
    type case3 = IsUnion<[string|number]>  // false
    

    答案

    type IsUnionImpl<T, C extends T = T> = 
        (T extends T 
            ? C extends T 
                ? true 
                : unknown 
            : never
        ) extends true ? false : true
    type IsUnion<T> = IsUnionImpl<T>
    

    ReplaceKeys

    Medium

    实现 ReplaceKeys 类型, 它将替换联合类型中类型的键值, 如果该类型没有这个Key则跳过,如果有则替换。

    type NodeA = {
      type: 'A'
      name: string
      flag: number
    }
    
    type NodeB = {
      type: 'B'
      id: number
      flag: number
    }
    
    type NodeC = {
      type: 'C'
      name: string
      flag: number
    }
    
    type Nodes = NodeA | NodeB | NodeC
    
    type ReplacedNodes = ReplaceKeys<Nodes, 'name' | 'flag', {name: number, flag: string}> // {type: 'A', name: number, flag: string} | {type: 'B', id: number, flag: string} | {type: 'C', name: number, flag: string} // would replace name from string to number, replace flag from number to string.
    
    type ReplacedNotExistKeys = ReplaceKeys<Nodes, 'name', {aa: number}> // {type: 'A', name: never, flag: number} | NodeB | {type: 'C', name: never, flag: number} // would replace name to never
    

    答案

    type ReplaceKeys<U, T, Y> = {
      [K in keyof U]: K extends T
        ? Y[keyof Y & K]
        : U[K]
    }
    

    Remove Index Signature

    Medium

    从对象类型中排除索引签名。

    type Foo = {
      [key: string]: any;
      foo(): void;
    }
    
    type A = RemoveIndexSignature<Foo>  // expected { foo(): void }
    
    

    答案

    https://github.com/type-challenges/type-challenges/issues/3542

    type RemoveIndexSignature<T> = {
        [K in keyof T as K extends `${infer _}` ? K : never]: T[K]
    }
    
    // `${infer _}` 不同于 string
    // 我们需要将K约束为字符串的值,而不是字符串类型
    

    Percentage Parser

    Medium

    实现类型 PercentageParser。根据规则 /^(\+|\-)?(\d*)?(\%)?$/ 匹配类型 T。

    匹配的结果由三部分组成,分别是:[正负号, 数字, 单位],如果没有匹配,则默认是空字符串。

    type PString1 = ''
    type PString2 = '+85%'
    type PString3 = '-85%'
    type PString4 = '85%'
    type PString5 = '85'
    
    type R1 = PercentageParser<PString1> // expected ['', '', '']
    type R2 = PercentageParser<PString2> // expected ["+", "85", "%"]
    type R3 = PercentageParser<PString3> // expected ["-", "85", "%"]
    type R4 = PercentageParser<PString4> // expected ["", "85", "%"]
    type R5 = PercentageParser<PString5> // expected ["", "85", ""]
    

    答案

    type Prefix<T extends string> = T extends `${infer P}${string}`
      ? P extends '-' | '+' ? P : ''
      : ''
    
    type Suffix<T extends string> = T extends `${string}%` ? '%' : ''
    
    type Num<T extends string> = T extends `${Prefix<T>}${infer R}${Suffix<T>}` ? R : never
    
    type PercentageParser<A extends string> = [Prefix<A>, Num<A>, Suffix<A>]
    

    Drop Char

    Medium

    从字符串中剔除指定字符。

    type Butterfly = DropChar<' b u t t e r f l y ! ', ' '> // 'butterfly!'
    

    答案

    type DropChar<S extends string, C extends string> = S extends `${infer H}${infer R}`
      ? `${H extends C ? '' : H}${DropChar<R, C>}`
      : S
    

    MinusOne

    Medium, Math

    给定一个正整数作为类型的参数,要求返回的类型是该数字减 1。

    type Zero = MinusOne<1> // 0
    type FiftyFour = MinusOne<55> // 54
    

    答案

    type MinusOne<T extends number, U extends number[] = []> = 
        U['length'] extends T 
        ? U[0] 
        : MinusOne<T, [U['length'], ...U]>
    // max 999 -> 998 TS最大递归次数
    

    PickByType

    Medium, object

    F 中选出类型相同的属性

    type OnlyBoolean = PickByType<{
      name: string
      count: number
      isReadonly: boolean
      isEnable: boolean
    }, boolean> // { isReadonly: boolean; isEnable: boolean; }
    
    

    答案

    type PickByType<T extends {}, U> = {
      [K in keyof T as T[K] extends U ? K : never]: T[K]
    }
    

    OmitByType

    Medium, #object

    保留没有在U中指定的类型的字段

    type OmitBoolean = OmitByType<{
      name: string
      count: number
      isReadonly: boolean
      isEnable: boolean
    }, boolean> // { name: string; count: number }
    

    答案

    type OmitByType<T extends {}, U> = {
    
      [K in keyof T as T[K] extends U ? never : K] : T[K]
    
    }
    

    StartsWith, EndsWith

    Medium#template-literal

    实现StartsWith<T, U>,接收两个string类型参数,然后判断T是否以U开头,根据结果返回truefalse

    type a = StartsWith<'abc', 'ac'> // expected to be false
    type b = StartsWith<'abc', 'ab'> // expected to be true
    type c = StartsWith<'abc', 'abcd'> // expected to be false
    

    答案

    type StartsWith<T extends string, U extends string> = 
        T extends `${U}${string}` ? true : false
    
    type EndsWith<T extends string, U extends string> = 
        T extends `${string}${U}` ? true : false
    

    PartialByKeys

    Medium, #object

    实现一个通用的PartialByKeys<T, K>,它接收两个类型参数TK

    K指定应设置为可选的T的属性集。当没有提供K时,它就和普通的Partial<T>一样使所有属性都是可选的。

    interface User {
      name: string
      age: number
      address: string
    }
    
    type UserPartialName = PartialByKeys<User, 'name'> // { name?:string; age:number; address:string }
    

    答案

    type Copy<T> = {
      [K in keyof T]:T[K]
    }
    
    type PartialByKeys<T , K extends keyof any = keyof T> = 
        Copy<Partial<Pick<T,Extract<keyof T, K>>> & Omit<T,K>>
    

    RequiredByKeys

    Medium, #object

    实现一个通用的RequiredByKeys<T, K>,它接收两个类型参数TK

    K指定应设为必选的T的属性集。当没有提供K时,它就和普通的Required<T>一样使所有的属性成为必选的。

    interface User {
      name?: string
      age?: number
      address?: string
    }
    
    type UserRequiredName = RequiredByKeys<User, 'name'> // { name: string; age?: number; address?: string }
    

    答案

    type Copy<T> = {
      [K in keyof T]:T[K]
    }
    
    type RequiredByKeys<T, K extends keyof any = keyof T> = 
        Copy<Required<Pick<T,Extract<keyof T, K>>> & Omit<T,K>>
    

    Mutable

    Medium, #readonly, object-keys

    实现一个通用的类型 Mutable<T>,使类型 T 的全部属性可变(非只读)。

    
    interface Todo {
      readonly title: string
      readonly description: string
      readonly completed: boolean
    }
    
    type MutableTodo = Mutable<Todo> // { title: string; description: string; completed: boolean; }
    

    答案

    type Mutable<T> = {
      -readonly[K in keyof T]: T[K]
    }
    

    ObjectEntries

    Medium, #object

    1

    interface Model {
      name: string;
      age: number;
      locations: string[] | null;
    }
    type modelEntries = ObjectEntries<Model> // ['name', string] | ['age', number] | ['locations', string[] | null];
    
    

    答案

    type ObjectEntries<T extends object, U = keyof T> = {
        [K in keyof T]-?: [
            K, Exclude<T[K], undefined> extends never 
                ? undefined 
                : Exclude<T[K], undefined>
        ]
    }[keyof T]
    

    Tuple to Nested Object

    Medium

    给定只包含字符串的元组,和类型U, 递归构建对象

    
    type a = TupleToNestedObject<['a'], string> // {a: string}
    type b = TupleToNestedObject<['a', 'b'], number> // {a: {b: number}}
    type c = TupleToNestedObject<[], boolean> // boolean. if the tuple is empty, just return the U type
    

    答案

    type TupleToNestedObject<T, U> = T extends [infer H extends string, ...infer R]
      ? { [K in H] : TupleToNestedObject<R, U> }
      : U
    

    Reverse

    Medium, #tuple

    实现类型版本的数组反转 Array.reverse

    
    type a = Reverse<['a', 'b']> // ['b', 'a']
    type b = Reverse<['a', 'b', 'c']> // ['c', 'b', 'a']
    

    答案

    type Reverse<T extends any[]> = 
        T extends [infer H, ...infer R] ? [...Reverse<R>, H] : []
    

    Flip Arguments

    Medium, #arguments

    实现类型版本的 lodash _.flip 函数

    类型 FlipArguments<T> 需要函数 T 并返回一个新的函数类型。这个函数类型拥有相同的参数,但参数类型是被反转的。

    type Flipped = FlipArguments<(arg0: string, arg1: number, arg2: boolean) => void> 
    // (arg0: boolean, arg1: number, arg2: string) => void
    
    

    答案

    type Reverse<T extends any[]> = 
        T extends [infer H, ...infer R] ? [...Reverse<R>, H] : []
    
    type FlipArguments<T extends (...args: any[]) => any> = 
        T extends (...args: infer P) => infer R 
            ? (...args: Reverse<P>) => R 
            : void
    

    FlattenDepth

    Medium, #array

    按深度递归展平阵列。

    type a = FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2> // [1, 2, 3, 4, [5]]. flattern 2 times
    type b = FlattenDepth<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, [[5]]]. Depth defaults to be 1
    
    

    答案

    type FlattenOnce<T extends any[]> =
      T extends [infer H, ...infer R]
        ? H extends any[]
          ? [...H, ...FlattenOnce<R>]
          : [H, ...FlattenOnce<R>]
        : T
    
    type FlattenDepth<
      T extends any[],
      D extends number = 1,
      Count extends 1[] = [],
      Flattened extends any[] = Count['length'] extends D 
          ? T 
          : FlattenOnce<T>,
      > = Flattened extends T ? T : FlattenDepth<Flattened, D, [...Count, 1]>
    

    BEM style string

    Medium

    块、元素、修饰符方法 (BEM) 是 CSS 中类的流行命名约定。例如,块组件将表示为 btn,依赖于块的元素将表示为 btn__price,改变块样式的修饰符将表示为 btn--bigbtn__price--warning。实现 BEM<B, E, M> 从这三个参数生成字符串联合。其中 B 是字符串文字,E 和 M 是字符串数组(可以为空)。

    
    BEM<'btn', ['price'], ['warning', 'success']
    // 'btn__price--warning' | 'btn__price--success'
    

    答案

    type BEM<B extends string, E extends string[], M extends string[]> =
      `${B}${E extends [] ? '' : `__${E[number]}`}${M extends [] ? '' : `--${M[number]}`}`
    

    InorderTraversal

    Medium,#object

    实现二叉树中序遍历的类型版本。

    const tree1 = {
      val: 1,
      left: null,
      right: {
        val: 2,
        left: {
          val: 3,
          left: null,
          right: null,
        },
        right: null,
      },
    } as const
    
    type A = InorderTraversal<typeof tree1> // [1, 3, 2]
    
    

    答案

    interface TreeNode {
      val: number
      left: TreeNode | null
      right: TreeNode | null
    }
    
    type InorderTraversal<T extends TreeNode | null> = 
        [T] extends [TreeNode] 
            ? [
                ...InorderTraversal<T['left']>, 
                T['val'], 
                ...InorderTraversal<T['right']>
            ] : []
    

    Flip

    Medium

    实现类型 just-flip-object:

    Flip<{ a: "x", b: "y", c: "z" }>; // {x: 'a', y: 'b', z: 'c'}
    Flip<{ a: 1, b: 2, c: 3 }>; // {1: 'a', 2: 'b', 3: 'c'}
    Flip<{ a: false, b: true }>; // {false: 'a', true: 'b'}
    
    

    答案

    type Flip<T extends any> = {
      [
        K in keyof T as T[K] extends keyof any
        ? T[K]
        : `${T[K] & (bigint | boolean | null | undefined)}`
      ]: K
    }
    

    Fibonacci Squence

    Medium

    实现泛型 Fibonacci<T> 传入数字 T 返回正确的 Fibonacci number.

    1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...

    type Result1 = Fibonacci<3> // 2
    type Result2 = Fibonacci<8> // 21
    

    答案

    type Fibonacci<
        T extends number, 
        N1 extends 1[] = [], 
        N2 extends 1[] = [1], 
        Count extends 1[] = [1]
    > =
      T extends 0 
          ? 0
          : Count['length'] extends T 
              ? N2['length']
              : Fibonacci<T, N2, [...N1, ...N2], [...Count, 1]>
    

    AllCombinations

    Medium

    实现类型 AllCombinations<S> 返回所有字符组合.

    type AllCombinations_ABC = AllCombinations<'ABC'>;
    // should be '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'
    

    答案

    type String2Union<S extends string> =
      S extends `${infer C}${infer REST}`
      ? C | String2Union<REST>
      : never
    
    type AllCombinations<
      STR extends string,
      S extends string = String2Union<STR>,
    > = [S] extends [never]
      ? ''
      : '' | {[K in S]: `${K}${AllCombinations<never, Exclude<S, K>>}`}[S]
    

    Greater Than

    Medium#array

    实现类型 GreaterThan<T, U> 来比较大小,就像 T > U 。不需要考虑负数

    1

    GreaterThan<2, 1> //should be true
    GreaterThan<1, 1> //should be false
    GreaterThan<10, 100> //should be false
    GreaterThan<111, 11> //should be true
    

    答案

    type GreaterThan<
      TA extends number,
      TB extends number,
      TArray extends unknown[] = [],
      TResult extends [boolean, boolean] = [
        TA extends TArray['length'] ? true : false,
        TB extends TArray['length'] ? true : false
      ]
    > = TA extends TB
      ? false
      : TResult extends [true, false]
        ? false
        : TResult extends [false, true]
          ? true
          : GreaterThan<TA, TB, [...TArray, unknown]>
    

    Zip

    Medium, #tuple

    实现 Zip<T, U> 类型。 T, U 必须为 Tuple

    type exp = Zip<[1, 2], [true, false]> // expected to be [[1, true], [2, false]]
    

    答案

    type Zip<T extends any[], U extends any[]> =
      T extends [infer TH, ...infer TR]
        ? U extends [infer UH, ...infer UR]
          ? [[TH, UH], ...Zip<TR, UR>]
          : []
        : []
    

    IsTuple

    Medium, #tuple

    实现 IsTuple, 接收类型 T 判断 T 是否为元组类型

    type case1 = IsTuple<[number]> // true
    type case2 = IsTuple<readonly [number]> // true
    type case3 = IsTuple<number[]> // false
    

    答案

    type IsTuple<T extends any> =
      [T] extends [never]
        ? false
        : T extends readonly []
          ? true
          : T extends [infer H, ...infer R] | readonly [infer H, ...infer R]
            ? true
            : false
    
    
    type IsTuple<T extends any> =
      [T] extends [never]
        ? false
        : T extends readonly any[]
          ? number extends T['length']
            ? false
            : true
          : false
    

    Chunk

    Medium#tuple

    实现 Chunk<T, N>, 它有两个必填的类型参数,T 必须为 tuple, N 必须为大于1的数字

    type exp1 = Chunk<[1, 2, 3], 2> // expected to be [[1, 2], [3]]
    type exp2 = Chunk<[1, 2, 3], 4> // expected to be [[1, 2, 3]]
    type exp3 = Chunk<[1, 2, 3], 1> // expected to be [[1], [2], [3]]
    

    答案

    type Chunk<
      T extends any[],
      N extends number,
      Result extends any[] = [],
      Cache extends any[] = []
    > = T extends [infer H, ...infer R]
      ? Cache['length'] extends N
        ? Chunk<R, N, [...Result, Cache], [H]>
        : Chunk<R, N, Result, [...Cache, H]>
      : Cache extends []
        ? Result
        : [...Result, Cache]
    

    Fill

    Medium, tuple

    Fill, 一个常用的 JavaScript 函数, 我们用类型实现它. Fill<T, N, Start?, End?>, 它接收4个类型参数, T , N 是必填参数 T为元组, N 为 any, Start , End 是可选参数,为大于零的数子.

    type exp = Fill<[1, 2, 3], 0> // expected to be [0, 0, 0]
    

    为了模拟真实的功能,测试中可能会包含一些边界条件,希望大家喜欢:)

    答案

    type Fill<
      TArray extends Array<unknown>,
      TN,
      TStart extends number = 0,
      TEnd extends number = TArray["length"],
      TResult extends Array<unknown> = [],
      TCanFill = false
    > = EmptyRange<TStart, TEnd> extends true
      ? TArray
      : TArray extends [infer First, ...infer Rest]
      ? TResult["length"] extends TStart
        ? Fill<Rest, TN, TStart, TEnd, [...TResult, TN], true>
        : TResult["length"] extends TEnd
        ? Fill<Rest, TN, TStart, TEnd, [...TResult, First], false>
        : Fill<Rest, TN, TStart, TEnd, [...TResult, (TCanFill extends true ? TN : First)], TCanFill>
    
      : TResult
    

    Trim Right

    Medium

    实现 TrimRight<T> 它采用精确的字符串类型并返回一个删除了空格结尾的新字符串。

    type Trimed = TrimRight<'   Hello World    '> // expected to be '   Hello World'
    

    答案

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

    Without

    Medium, #union, #array

    实现一个像 Lodash.without 函数一样的泛型 Without<T, U>,它接收数组类型的 T 和数字或数组类型的 U 为参数,会返回一个去除 U 中元素的数组 T。

    
    type Res = Without<[1, 2], 1>; // expected to be [2]
    type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // expected to be [4, 5]
    type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // expected to be []
    

    答案

    type Without<T, U> =
      T extends [U extends any[] ? U[number] : U, ...infer R]
        ? Without<R, U>
        : T extends [infer H, ...infer R]
          ? [H, ...Without<R, U>]
          : T
    

    Trunc

    Medium, template-literal

    实现类型版本的 Math.trunc. 它接受字符串或数字返回整数部分,提出小数部分

    type A = Trunc<12.34> // 12
    

    答案

    type Trunc<N extends number | string> = `${N}` extends `${infer H}.${string}` 
        ? H 
        : `${N}`
    

    IndexOf

    Medium, #array

    实现类型版本的 Array.indexOf<T, U>, 它接收数组T 和 U 返回U在T中的索引值

    type Res = IndexOf<[1, 2, 3], 2>; // expected to be 1
    type Res1 = IndexOf<[2,6, 3,8,4,1,7, 3,9], 3>; // expected to be 2
    type Res2 = IndexOf<[0, 0, 0], 2>; // expected to be -1
    

    答案

    type IndexOf<T extends any[], U, Cache extends unknown[] = []> =
      T extends [infer H, ...infer R]
        ? Equal<H, U> extends true
          ? Cache['length']
          : IndexOf<R, U, [unknown, ...Cache]>
        : -1
    

    Join

    Medium, #array

    实现类型版 Array.join<T, U> 接收数组T和字符串或数字 U

    type Res = Join<["a", "p", "p", "l", "e"], "-">; // expected to be 'a-p-p-l-e'
    type Res1 = Join<["Hello", "World"], " ">; // expected to be 'Hello World'
    type Res2 = Join<["2", "2", "2"], 1>; // expected to be '21212'
    type Res3 = Join<["o"], "u">; // expected to be 'o'
    

    答案

    type Join<T, U extends string | number, Result extends string = ''> =
      T extends [infer H, ...infer R]
        ? Join<R, U, Result extends '' ? H : `${Result}${U}${H & string}`>
        : Result
    

    LastIndexOf

    Medium, #array

    实现类型版本的 Array.lastIndexOf<T, U>, 它接收数组T 和 U 返回U在T中的反向索引值

    type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2> // 3
    type Res2 = LastIndexOf<[0, 0, 0], 2> // -1
    

    答案

    type LastIndexOf<O extends any[], U, Cache extends unknown[] = []> =
      O extends [...infer R, infer T]
        ? Equal<T, U> extends true
          ? R['length']
          : LastIndexOf<R, U, [...Cache, unknown]>
        : -1
    

    Unique

    Medium, #array

    实现类型版本的Lodash.uniq, 它接收数组T,返回去重后的T

    type Res = Unique<[1, 1, 2, 2, 3, 3]>; // expected to be [1, 2, 3]
    type Res1 = Unique<[1, 2, 3, 4, 4, 5, 6, 7]>; // expected to be [1, 2, 3, 4, 5, 6, 7]
    type Res2 = Unique<[1, "a", 2, "b", 2, "a"]>; // expected to be [1, "a", 2, "b"]
    type Res3 = Unique<[string, number, 1, "a", 1, string, 2, "b", 2, number]>; // expected to be [string, number, 1, "a", 2, "b"]
    type Res4 = Unique<[unknown, unknown, any, any, never, never]>; // expected to be [unknown, any, never]
    

    答案

    type IsIncludes<T extends any[], U> = T extends [infer F, ...infer R]
      ? Equal<F, U> extends true
        ? true
        : IsIncludes<R, U>
      : false
    
    type Unique<T extends any[], U extends any[] = []> =
      T extends [infer H, ...infer R]
        ? IsIncludes<U, H> extends false
          ? Unique<R, [...U, H]>
          : Unique<R, U>
        : U
    

    MapTypes

    Medium

    实现 MapTypes<T, R> 它将对象 T 中的类型转换为类型 R 定义的不同类型,类型 R 具有以下结构。

    
    type StringToNumber = {
      mapFrom: string; // value of key which value is string
      mapTo: number; // will be transformed for number
    }
    
    type StringToNumber = { mapFrom: string; mapTo: number;}
    type a = MapTypes<{iWillBeANumberOneDay: string}, StringToNumber> // gives { iWillBeANumberOneDay: number; }
    
    type StringToDate = { mapFrom: string; mapTo: Date;}
    
    type b = MapTypes<{iWillBeNumberOrDate: string}, StringToDate | StringToNumber> // gives { iWillBeNumberOrDate: number | Date; }
    
    
    type c = MapTypes<{iWillBeANumberOneDay: string, iWillStayTheSame: Function}, StringToNumber> // // gives { iWillBeANumberOneDay: number, iWillStayTheSame: Function }
    
    

    答案

    type GetMapToType<
      T,
      R,
      Type = R extends { mapFrom: T; mapTo: infer To } ? To : never
    > = [Type] extends [never] ? T : Type
    
    type MapTypes<T, R> = {
      [key in keyof T]: GetMapToType<T[key], R>
    }
    

    Construct Tuple

    Medium, #tuple

    构造一个给定长度的元组

    type result = ConstructTuple<2> // expect to be [unknown, unkonwn]
    

    答案

    type ConstructTuple<L extends number, Res extends unknown[] = []> =
      L extends Res['length']
        ? Res
        : ConstructTuple<L, [...Res, unknown]>
    

    Number Range

    Medium

    有时我们想限制数字的范围......例如。

    type result = NumberRange<2 , 9> //  | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 
    

    答案

    type NumberRange<
      T extends number,
      U extends number,
      R extends any[] = [],
      L extends any[] = [],
      S extends any[] = []
    > = T extends R['length']
      ? U extends L['length']
        ? S[number] | L['length']
        : NumberRange<T, U, R, [...L, T], [...S, L['length']]>
      : NumberRange<T, U, [...R, T], [...R, T], S>
    

    Combination

    Medium, #array, #application, #string

    给定一个字符串数组,进行置换和组合。它对于像video controlsList这样的类型也很有用

    // expected to be `"foo" | "bar" | "baz" | "foo bar" | "foo bar baz" | "foo baz" | "foo baz bar" | "bar foo" | "bar foo baz" | "bar baz" | "bar baz foo" | "baz foo" | "baz foo bar" | "baz bar" | "baz bar foo"`
    type Keys = Combination<['foo', 'bar', 'baz']>
    
    

    答案

    type Combination<T extends string[], U = T[number], A = U> = 
      U extends infer U extends string
        ? `${U} ${Combination<T, Exclude<A, U>>}` | U
        : never
    

    Subsequence

    Medium, #union

    给定一个唯一元素数组,返回所有可能的子序列。

    子序列是一个序列,可以通过删除一些元素或不删除任何元素而从数组中派生,而不改变其余元素的顺序。

    type A = Subsequence<[1, 2]> // [] | [1] | [2] | [1, 2]
    

    答案

    type Subsequence<T extends unknown[]> = T extends [infer X, ...infer Y]
      ? [X, ...Subsequence<Y>] | Subsequence<Y>
      : []
    

    相关文章

      网友评论

          本文标题:TypeScript 类型挑战 Medium

          本文链接:https://www.haomeiwen.com/subject/pcadbrtx.html