TypeScript 类型挑战 Medium

TypeScript 类型挑战 Medium

[[toc]]

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

Medium 版需要注意的事情

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

Get Return Type

Medium, #infer, #built-in

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

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

type a = MyReturnType // 应推导出 "1 | 2"

答案

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

Omit

Medium, #union, #built-in

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

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

type TodoPreview = MyOmit

const todo: TodoPreview = {
  completed: false,
}

答案

type MyExclude = T extends K ? never : T

type MyOmit = {
  [V in MyExclude]: T[V]
}

Readonly 2

Medium, #readonly, #object-keys

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

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

interface Todo {

  title: string
  description: string
  completed: boolean
  
}

const todo: MyReadonly2 = {

  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 = {

  [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,它将对象的每个参数及其子对象递归地设为只读。

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

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 // should be same as `Expected`

答案

type DeepReadonly = {

  readonly [K in keyof T]: keyof T[K] extends never ? T[K] : DeepReadonly

}

Tuple to Union

Medium, #infer, #tuple, #union

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

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

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

答案


type TupleToUnion = 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 = {

  option(key: K, value: V)
    : Chainable
  get(): T

}

Last of Array

Medium, #array

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

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

type tail1 = Last // expected to be 'c'
type tail2 = Last // expected to be 1

答案

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

Pop, Shift, Push, Unshift

Medium, #array

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


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

type re1 = Pop // expected to be ['a', 'b', 'c']
type re2 = Pop // expected to be [3, 2]

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

答案


type Pop = T extends [... infer R, infer last] ? R : T

type Push = [...T, V]

type Shift = T extends [infer head, ... infer R] ? R : T

type Unshift = [V, ...T]

Promise.all

Medium, #array, #built-in

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

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((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(values: readonly [...T])
  : Promise<{ [K in keyof T] : T[K] extends Promise ? R : T[K] }>

Type Lookup

Medium, #union, `#map

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

在此挑战中,我们想通过在联合类型Cat | Dog中搜索公共type字段来获取相应的类型。换句话说,在以下示例中,我们期望LookUp获得DogLookUp获得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 // expected to be `Dog`

答案

type LookUp = U extends { type: infer T } 
    ? V extends T ? U : never 
    : never

Trim Left

Medium, #template-literal

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

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

答案

type TrimChar = ' ' | '\n' | '\t'

type TrimLeft = 
    S extends `${TrimChar}${infer R}` ? TrimLeft : S

Trim

Medium, #template-literal

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

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

答案

type TrimChar = ' ' | '\n' | '\t'

type TrimLeft =
  S extends `${TrimChar}${infer R}` ? TrimLeft : S

type TrimRight =
  S extends `${infer L}${TrimChar}` ? TrimRight : S


type Trim = TrimLeft>

Capitalize

Medium, #template-literal

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

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

答案

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

Replace

Medium, #template-iteral

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

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

答案

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

ReplaceAll

Medium, #template-literal

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

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

答案

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

Append Argument

Medium, #arguments

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

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

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

答案

type AppendArgument 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] extends [never]
        ? []
        : K extends K
            ? [K, ...Permutation>]
            : 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 `${infer H}${infer R}` 
        ? [H, ...StringToArray] 
        : []

type LengthOfString = StringToArray['length']

Flatten

Medium, #array

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

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

答案

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

Append to object

Medium, #object-keys

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

type Test = { id: '1' }
type Result = AppendToObject // 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; // expected to be "100"

答案

type Absolute = `${T}` extends `-${infer X}` ? X : `${T}`

String to Union

Medium, #union , #string

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

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

答案

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

Merge

Medium, #object

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

type foo = {
  name: string;
  age: string;
}
type coo = {
  age: number;
  sex: string
}

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

答案

type Merge = {
  [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 `${infer H}${infer R}`
  ? H extends Lowercase
    ? `${H}${_KebaCase}`
    : `-${Lowercase}${_KebaCase}`
  : ''

  

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

Diff

Medium, #object

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

type Foo = {
  a: string;
  b: number;
}
type Bar = {
  a: string;
  c: boolean
}

type Result1 = Diff // { b: number, c: boolean }
type Result2 = Diff // { b: number, c: boolean }

答案

type Diff = {
  [K in Exclude | Exclude] :
    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 | 0
type AnyOf = T[number] extends Falsy ? false : true

IsNever

Medium, #union , #utils

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

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

答案

type IsNever = [T] extends [never] ? true : false

// note T extends never 无法判定

IsUnion

Medium, #union , #utils

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

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

答案

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

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 // {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 // {type: 'A', name: never, flag: number} | NodeB | {type: 'C', name: never, flag: number} // would replace name to never

答案

type ReplaceKeys = {
  [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  // expected { foo(): void }

答案

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

type RemoveIndexSignature = {
    [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 // expected ['', '', '']
type R2 = PercentageParser // expected ["+", "85", "%"]
type R3 = PercentageParser // expected ["-", "85", "%"]
type R4 = PercentageParser // expected ["", "85", "%"]
type R5 = PercentageParser // expected ["", "85", ""]

答案

type Prefix = T extends `${infer P}${string}`
  ? P extends '-' | '+' ? P : ''
  : ''

type Suffix = T extends `${string}%` ? '%' : ''

type Num = T extends `${Prefix}${infer R}${Suffix}` ? R : never

type PercentageParser = [Prefix, Num, Suffix]

Drop Char

Medium

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

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

答案

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

MinusOne

Medium, Math

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

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

答案

type MinusOne = 
    U['length'] extends T 
    ? U[0] 
    : MinusOne
// 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 = {
  [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 = {

  [K in keyof T as T[K] extends U ? never : K] : T[K]

}

StartsWith, EndsWith

Medium#template-literal

实现StartsWith,接收两个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 `${U}${string}` ? true : false

type EndsWith = 
    T extends `${string}${U}` ? true : false

PartialByKeys

Medium, #object

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

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

interface User {
  name: string
  age: number
  address: string
}

type UserPartialName = PartialByKeys // { name?:string; age:number; address:string }

答案

type Copy = {
  [K in keyof T]:T[K]
}

type PartialByKeys = 
    Copy>> & Omit>

RequiredByKeys

Medium, #object

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

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

interface User {
  name?: string
  age?: number
  address?: string
}

type UserRequiredName = RequiredByKeys // { name: string; age?: number; address?: string }

答案

type Copy = {
  [K in keyof T]:T[K]
}

type RequiredByKeys = 
    Copy>> & Omit>

Mutable

Medium, #readonly, object-keys

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


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

type MutableTodo = Mutable // { title: string; description: string; completed: boolean; }

答案

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

ObjectEntries

Medium, #object

1

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

答案

type ObjectEntries = {
    [K in keyof T]-?: [
        K, Exclude extends never 
            ? undefined 
            : Exclude
    ]
}[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 extends [infer H extends string, ...infer R]
  ? { [K in H] : TupleToNestedObject }
  : 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 [infer H, ...infer R] ? [...Reverse, H] : []

Flip Arguments

Medium, #arguments

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

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

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

答案

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

type FlipArguments any> = 
    T extends (...args: infer P) => infer R 
        ? (...args: Reverse

) => 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 [infer H, ...infer R]
    ? H extends any[]
      ? [...H, ...FlattenOnce]
      : [H, ...FlattenOnce]
    : T

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

BEM style string

Medium

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


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

答案

type BEM =
  `${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 // [1, 3, 2]

答案

interface TreeNode {
  val: number
  left: TreeNode | null
  right: TreeNode | null
}

type InorderTraversal = 
    [T] extends [TreeNode] 
        ? [
            ...InorderTraversal, 
            T['val'], 
            ...InorderTraversal
        ] : []

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 = {
  [
    K in keyof T as T[K] extends keyof any
    ? T[K]
    : `${T[K] & (bigint | boolean | null | undefined)}`
  ]: K
}

Fibonacci Squence

Medium

实现泛型 Fibonacci 传入数字 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

AllCombinations

Medium

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

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 `${infer C}${infer REST}`
  ? C | String2Union
  : never

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

Greater Than

Medium#array

实现类型 GreaterThan 来比较大小,就像 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

Zip

Medium, #tuple

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

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

答案

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

IsTuple

Medium, #tuple

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

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

答案

type IsTuple =
  [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 [never]
    ? false
    : T extends readonly any[]
      ? number extends T['length']
        ? false
        : true
      : false

Chunk

Medium#tuple

实现 Chunk, 它有两个必填的类型参数,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
    : Chunk
  : Cache extends []
    ? Result
    : [...Result, Cache]

Fill

Medium, tuple

Fill, 一个常用的 JavaScript 函数, 我们用类型实现它. Fill, 它接收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,
  TN,
  TStart extends number = 0,
  TEnd extends number = TArray["length"],
  TResult extends Array = [],
  TCanFill = false
> = EmptyRange extends true
  ? TArray
  : TArray extends [infer First, ...infer Rest]
  ? TResult["length"] extends TStart
    ? Fill
    : TResult["length"] extends TEnd
    ? Fill
    : Fill

  : TResult

Trim Right

Medium

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

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

答案

type TrimChar = ' ' | '\n' | '\t'

type TrimRight =
  S extends `${infer R}${TrimChar}` ? TrimRight: S

Without

Medium, #union, #array

实现一个像 Lodash.without 函数一样的泛型 Without,它接收数组类型的 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 extends [U extends any[] ? U[number] : U, ...infer R]
    ? Without
    : T extends [infer H, ...infer R]
      ? [H, ...Without]
      : T

Trunc

Medium, template-literal

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

type A = Trunc<12.34> // 12

答案

type Trunc = `${N}` extends `${infer H}.${string}` 
    ? H 
    : `${N}`

IndexOf

Medium, #array

实现类型版本的 Array.indexOf, 它接收数组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 [infer H, ...infer R]
    ? Equal extends true
      ? Cache['length']
      : IndexOf
    : -1

Join

Medium, #array

实现类型版 Array.join 接收数组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 extends [infer H, ...infer R]
    ? Join
    : Result

LastIndexOf

Medium, #array

实现类型版本的 Array.lastIndexOf, 它接收数组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 [...infer R, infer T]
    ? Equal extends true
      ? R['length']
      : LastIndexOf
    : -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 [infer F, ...infer R]
  ? Equal extends true
    ? true
    : IsIncludes
  : false

type Unique =
  T extends [infer H, ...infer R]
    ? IsIncludes extends false
      ? Unique
      : Unique
    : U

MapTypes

Medium

实现 MapTypes 它将对象 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 = {
  [key in keyof T]: GetMapToType
}

Construct Tuple

Medium, #tuple

构造一个给定长度的元组

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

答案

type ConstructTuple =
  L extends Res['length']
    ? Res
    : ConstructTuple

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
  : NumberRange

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 = 
  U extends infer U extends string
    ? `${U} ${Combination>}` | U
    : never

Subsequence

Medium, #union

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

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

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

答案

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

你可能感兴趣的:(TypeScript 类型挑战 Medium)