TypeScript 類型挑戰(zhàn) Medium

TypeScript 類型挑戰(zhàn) Medium

[[toc]]

  • 項(xiàng)目地址 Github
  • 項(xiàng)目描述: 高質(zhì)量的類型可以幫助提高項(xiàng)目的可維護(hù)性,同時(shí)避免潛在的錯(cuò)誤。

Medium 版需要注意的事情

  • 這部分挑戰(zhàn)有很多使用遞歸來實(shí)現(xiàn)。還涉及很多類型語(yǔ)言的特殊用法。比如如何判斷 never。利用數(shù)組來計(jì)數(shù)等等。

Get Return Type

Medium, #infer, #built-in

實(shí)現(xiàn) TS 內(nèi)置的 ReturnType<T>,但不可以使用它。

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

type a = MyReturnType<typeof fn> // 應(yīng)推導(dǎo)出 "1 | 2"

答案

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

Omit

Medium, #union, #built-in

不使用 Omit 實(shí)現(xiàn) TypeScript 的 Omit<T, K> 泛型。
Omit 會(huì)創(chuàng)建一個(gè)省略 K 中字段的 T 對(duì)象。

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

實(shí)現(xiàn)一個(gè)通用MyReadonly2<T, K>,它帶有兩種類型的參數(shù)TK。

K指定應(yīng)設(shè)置為Readonly的T的屬性集。如果未提供K,則應(yīng)使所有屬性都變?yōu)橹蛔x,就像普通的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

實(shí)現(xiàn)一個(gè)通用的DeepReadonly<T>,它將對(duì)象的每個(gè)參數(shù)及其子對(duì)象遞歸地設(shè)為只讀。

您可以假設(shè)在此挑戰(zhàn)中我們僅處理對(duì)象。數(shù)組,函數(shù),類等都無需考慮。但是,您仍然可以通過覆蓋盡可能多的不同案例來挑戰(zhàn)自己。

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

實(shí)現(xiàn)泛型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 中我們很常會(huì)使用可串聯(lián)(Chainable/Pipeline)的函數(shù)構(gòu)造一個(gè)對(duì)象,但在 TypeScript 中,你能合理的給他附上類型嗎?

在這個(gè)挑戰(zhàn)中,你可以使用任意你喜歡的方式實(shí)現(xiàn)這個(gè)類型 - Interface, Type 或 Class 都行。你需要提供兩個(gè)函數(shù) option(key, value)get()。在 option 中你需要使用提供的 key 和 value 擴(kuò)展當(dāng)前的對(duì)象類型,通過 get 獲取最終結(jié)果。

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

你只需要在類型層面實(shí)現(xiàn)這個(gè)功能 - 不需要實(shí)現(xiàn)任何 TS/JS 的實(shí)際邏輯。

你可以假設(shè) key 只接受字符串而 value 接受任何類型,你只需要暴露它傳遞的類型而不需要進(jìn)行任何處理。同樣的 key 只會(huì)被使用一次。

答案

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

實(shí)現(xiàn)一個(gè)通用Last<T>,它接受一個(gè)數(shù)組T并返回其最后一個(gè)元素的類型。

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

實(shí)現(xiàn)一個(gè)通用Pop<T>,它接受一個(gè)數(shù)組T并返回一個(gè)沒有最后一個(gè)元素的數(shù)組。


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]

額外:同樣,您也可以實(shí)現(xiàn)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

鍵入函數(shù)PromiseAll,它接受PromiseLike對(duì)象數(shù)組,返回值應(yīng)為Promise<T>,其中T是解析的結(jié)果數(shù)組。

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

有時(shí),您可能希望根據(jù)某個(gè)屬性在聯(lián)合類型中查找類型。

在此挑戰(zhàn)中,我們想通過在聯(lián)合類型Cat | Dog中搜索公共type字段來獲取相應(yīng)的類型。換句話說,在以下示例中,我們期望LookUp<Dog | Cat, 'dog'>獲得Dog,LookUp<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

實(shí)現(xiàn) TrimLeft<T> ,它接收確定的字符串類型并返回一個(gè)新的字符串,其中新返回的字符串刪除了原字符串開頭的空白字符串。

type trimed = TrimLeft<'  Hello World  '> // 應(yīng)推導(dǎo)出 'Hello World  '

答案

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

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

Trim

Medium, #template-literal

實(shí)現(xiàn)Trim<T>,它是一個(gè)字符串類型,并返回一個(gè)新字符串,其中兩端的空白符都已被刪除。

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

實(shí)現(xiàn) Capitalize<T> 它將字符串的第一個(gè)字母轉(zhuǎn)換為大寫,其余字母保持原樣。

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

實(shí)現(xiàn) Replace<S, From, To> 將字符串 S 中的第一個(gè)子字符串 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

實(shí)現(xiàn) ReplaceAll<S, From, To> 將一個(gè)字符串 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

實(shí)現(xiàn)一個(gè)泛型 AppendArgument<Fn, A>,對(duì)于給定的函數(shù)類型 Fn,以及一個(gè)任意類型 A,返回一個(gè)新的函數(shù) GG 擁有 Fn 的所有參數(shù)并在末尾追加類型為 A 的參數(shù)。

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

實(shí)現(xiàn)聯(lián)合類型的全排列,將聯(lián)合類型轉(zhuǎn)換成所有可能的全排列數(shù)組的聯(lián)合類型。

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

計(jì)算字符串的長(zhǎng)度,類似于 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

在這個(gè)挑戰(zhàn)中,你需要寫一個(gè)接受數(shù)組的類型,并且返回扁平化的數(shù)組類型。

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

實(shí)現(xiàn)一個(gè)為接口添加一個(gè)新字段的類型。該類型接收三個(gè)參數(shù),返回帶有新字段的接口類型。

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

實(shí)現(xiàn)一個(gè)接收string,number或bigInt類型參數(shù)的Absolute類型,返回一個(gè)正數(shù)字符串。

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

實(shí)現(xiàn)一個(gè)將接收到的String參數(shù)轉(zhuǎn)換為一個(gè)字母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

實(shí)現(xiàn)聯(lián)合類型的全排列,將聯(lián)合類型轉(zhuǎn)換成所有可能的全排列數(shù)組的聯(lián)合類型。

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

獲取兩個(gè)接口類型中的差值屬性。

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

在類型系統(tǒng)中實(shí)現(xiàn)類似于 Python 中 any 函數(shù)。類型接收一個(gè)數(shù)組,如果數(shù)組中任一個(gè)元素為真,則返回 true,否則返回 false。如果數(shù)組為空,返回 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

實(shí)現(xiàn) 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

實(shí)現(xiàn) IsUnion 類型, 解析輸入 T 類型為聯(lián)合類型 返回 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

實(shí)現(xiàn) ReplaceKeys 類型, 它將替換聯(lián)合類型中類型的鍵值, 如果該類型沒有這個(gè)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

從對(duì)象類型中排除索引簽名。

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

實(shí)現(xiàn)類型 PercentageParser。根據(jù)規(guī)則 /^(\+|\-)?(\d*)?(\%)?$/ 匹配類型 T。

匹配的結(jié)果由三部分組成,分別是:[正負(fù)號(hào), 數(shù)字, 單位],如果沒有匹配,則默認(rèn)是空字符串。

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

給定一個(gè)正整數(shù)作為類型的參數(shù),要求返回的類型是該數(shù)字減 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最大遞歸次數(shù)

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

實(shí)現(xiàn)StartsWith<T, U>,接收兩個(gè)string類型參數(shù),然后判斷T是否以U開頭,根據(jù)結(jié)果返回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

實(shí)現(xiàn)一個(gè)通用的PartialByKeys<T, K>,它接收兩個(gè)類型參數(shù)TK。

K指定應(yīng)設(shè)置為可選的T的屬性集。當(dāng)沒有提供K時(shí),它就和普通的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

實(shí)現(xiàn)一個(gè)通用的RequiredByKeys<T, K>,它接收兩個(gè)類型參數(shù)TK。

K指定應(yīng)設(shè)為必選的T的屬性集。當(dāng)沒有提供K時(shí),它就和普通的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

實(shí)現(xiàn)一個(gè)通用的類型 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, 遞歸構(gòu)建對(duì)象


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

實(shí)現(xiàn)類型版本的數(shù)組反轉(zhuǎn) 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

實(shí)現(xiàn)類型版本的 lodash _.flip 函數(shù)

類型 FlipArguments<T> 需要函數(shù) T 并返回一個(gè)新的函數(shù)類型。這個(gè)函數(shù)類型擁有相同的參數(shù),但參數(shù)類型是被反轉(zhuǎn)的。

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。實(shí)現(xiàn) BEM<B, E, M> 從這三個(gè)參數(shù)生成字符串聯(lián)合。其中 B 是字符串文字,E 和 M 是字符串?dāng)?shù)組(可以為空)。


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

實(shí)現(xiàn)二叉樹中序遍歷的類型版本。

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

實(shí)現(xiàn)類型 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

實(shí)現(xiàn)泛型 Fibonacci<T> 傳入數(shù)字 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

實(shí)現(xiàn)類型 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

實(shí)現(xiàn)類型 GreaterThan<T, U> 來比較大小,就像 T > U 。不需要考慮負(fù)數(shù)

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

實(shí)現(xiàn) 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

實(shí)現(xiàn) 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

實(shí)現(xiàn) Chunk<T, N>, 它有兩個(gè)必填的類型參數(shù),T 必須為 tuple, N 必須為大于1的數(shù)字

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, 一個(gè)常用的 JavaScript 函數(shù), 我們用類型實(shí)現(xiàn)它. Fill<T, N, Start?, End?>, 它接收4個(gè)類型參數(shù), T , N 是必填參數(shù) T為元組, N 為 any, Start , End 是可選參數(shù),為大于零的數(shù)子.

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

為了模擬真實(shí)的功能,測(cè)試中可能會(huì)包含一些邊界條件,希望大家喜歡:)

答案

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

實(shí)現(xiàn) TrimRight<T> 它采用精確的字符串類型并返回一個(gè)刪除了空格結(jié)尾的新字符串。

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

實(shí)現(xiàn)一個(gè)像 Lodash.without 函數(shù)一樣的泛型 Without<T, U>,它接收數(shù)組類型的 T 和數(shù)字或數(shù)組類型的 U 為參數(shù),會(huì)返回一個(gè)去除 U 中元素的數(shù)組 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

實(shí)現(xiàn)類型版本的 Math.trunc. 它接受字符串或數(shù)字返回整數(shù)部分,提出小數(shù)部分

type A = Trunc<12.34> // 12

答案

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

IndexOf

Medium, #array

實(shí)現(xiàn)類型版本的 Array.indexOf<T, U>, 它接收數(shù)組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

實(shí)現(xiàn)類型版 Array.join<T, U> 接收數(shù)組T和字符串或數(shù)字 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

實(shí)現(xiàn)類型版本的 Array.lastIndexOf<T, U>, 它接收數(shù)組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

實(shí)現(xiàn)類型版本的Lodash.uniq, 它接收數(shù)組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

實(shí)現(xiàn) MapTypes<T, R> 它將對(duì)象 T 中的類型轉(zhuǎn)換為類型 R 定義的不同類型,類型 R 具有以下結(jié)構(gòu)。


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

構(gòu)造一個(gè)給定長(zhǎng)度的元組

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

有時(shí)我們想限制數(shù)字的范圍......例如。

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

給定一個(gè)字符串?dāng)?shù)組,進(jìn)行置換和組合。它對(duì)于像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

給定一個(gè)唯一元素?cái)?shù)組,返回所有可能的子序列。

子序列是一個(gè)序列,可以通過刪除一些元素或不刪除任何元素而從數(shù)組中派生,而不改變其余元素的順序。

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>
  : []
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容