跳到主要内容

工具类型实现

内置类型

Partial<T>

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

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}

const todo1 = {
title: "organize desk",
description: "clear clutter"
};

const todo2 = updateTodo(todo1, {
description: "throw out trash"
});

// implement
type Partial<T> = {
[P in keyof T]?: T[P]
}

Required<T>

type Required<T> = {
[P in keyof T]-?: T[P]
}

Readonly<T>

type Readonly<T> = {
readonly [P in keyof T]: T[P]
}

Pick<T, K>

// example
interface Todo {
title: string;
description: string;
completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;

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

// implement
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}

Omit<T, K>

// example
interface Todo {
title: string;
description: string;
completed: boolean;
}

type TodoPreview = Omit<Todo, "description">;

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

// implement

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
// or
type Omit<T, K> = {
[P in Exclude<keyof T, K>]: T[P]
}

Extract<T, U>

type Extract<T, K> = T extends K ? T : never

Exclude<T, U>

// example
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number

// implement
// extends搭配 ?: 运算符使用
type Exclude<T, U> = T extends U ? never : T;

// 相当于: type A = 'a'
type A = Exclude<'x' | 'a', 'x' | 'y' | 'z'>

Record<K, T>

type Record<K extends keyof any, T> = {
[P in K]: T
}

NonNullable<T>

type NonNullable<T extends keyof any> = T extends null | undefined ? never : T

Parameters<T>

// infer 关键字
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

ReturnType<T>

// infer 关键字
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : never

ConstructorParameters<T>

type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;

InstanceType<T>

type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

Awaited<T>

type MyAwaited<T> = T extends Promise<infer T> ? MyAwaited<T> : T;

其他

  • Uppercase,全体大写
  • Lowercase,全体小些
  • Capitalize,首字母大写
  • Uncapitalize,首字母小写

Equal

非内置工具类型,用于比较两个类型完全相等,因为其高泛用性和实现的特殊性所以特别拎出来讨论。社区主流的实现如下:

export type Equal<X, Y> =
(
(<T>() => (T extends X ? 1 : 2)) extends
(<T>() => (T extends Y ? 1 : 2))
)
? true
: false;

Type Challenge

开始做题

简单题

Tuple To Object

问题的本质是元祖如何转化为联合类型

type TupleToObject<T extends readonly any[]> = {
[k in T[number]]: k
}

First Of Array

type First<T extends any[]> = T extends [infer F, ...infer R] ? F : never;

Length Of Tuple

需要注意的是需要用readonly any[],否则元祖会走到never分支

type Length<T> = T extends readonly any[] ? T['length'] : never;

Concat

type Concat<T extends readonly any[], U extends readonly any[]> = [...T, ...U]

Include

参考,需要借助额外实现的Equal类型

// 迭代写法
type IterativeIncludes<T extends readonly any[], U> = {
[K in keyof T]: Equal<T[K], U>;
}[number] extends false
? false
: true;

// 递归写法
type RecursiveIncludes<T extends readonly any[], U> = T extends [
infer F,
...infer R
]
? Equal<F, U> extends true
? true
: RecursiveIncludes<R, U>
: false;

中等题

Readonly2

type MyReadonly2<T, K extends keyof T = keyof T> = {
[k in Exclude<keyof T, K>]: T[k]
} & {
readonly [k in K]: T[k]
}

Deep Readonly

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

Tuple To Union

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

Chainable Options

type Chainable<T = {}> = {
option<K extends string, V extends any>(key: K, value: Exclude<V, K extends keyof T ? T[K] : never>): Chainable<{
[k in (keyof T) | K]: k extends keyof T ? K extends keyof T ? V : T[k] : V
}>
get(): T
}

注意点

  1. option的返回值中需要进行字段的覆盖,不用单纯用&,除了这里写的方法也可以先Omit&
  2. 需要对option的参数value的类型V进行约束,比如在某些情况下V的类型不能是string

扩展阅读

Exclude<T, string> = 'hi',一元方程式求解,自动推导出T的类型为'hi',arg为never

const fn = <T extends any>(arg: Exclude<T, string>) => arg;
fn('hi') // const fn: <"hi">(arg: never) => never

Promise.all

type Util<T extends readonly any[]> = T extends readonly [infer F, ...infer L] ? [Awaited<F>, ...Util<L>] : []
declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<Util<typeof values>>

注意点

  1. 这里通过extends [infer F]来递归获取元组的参数,但是数组不会跑到这个分支,所以我们需要一种手段把数组转化为等效的元组,通过values: readonly [...T]实现

Type Lookup

type LookUp<U, T> = U extends {
type: T
} ? U : never;

Trim Left

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

Capitalize

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

Replace

type Replace<S extends string, From extends string, To extends string> = S extends `${infer L}${Exclude<From, ''>}${infer R}` ? `${L}${To}${R}` : S;

ReplaceAll

type ReplaceAll<S extends string, From extends string, To extends string> = S extends `${infer L}${Exclude<From, ''>}${infer R}` ? `${L}${To}${ReplaceAll<R, From, To>}` : S 

Amend Argument

type AppendArgument<Fn extends (...args: any[]) => any, A> = (...args: [...Parameters<Fn>, A]) => ReturnType<Fn>

Permutation

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

注意点

  1. 触发类型分发后,为了仍然能够获取完整的联合类型,我们额外声明了一个类型参数K = T

Length Of String

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

思路:字符串转元组

Flatten

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

Amend To Object

type AppendToObject<T, U extends keyof any, V> = {
[K in (keyof T | U)]: K extends keyof T ? T[K] : V
}

Absolute

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

思路:数字转字符串

String To Union

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

Merge

type Merge<F, S> = {
[K in keyof F | keyof S]: K extends keyof S ? S[K] : K extends keyof F ? F[K] : never;
}

Diff

type Diff<T, U> = Pick<{
[K in keyof T | keyof U]: K extends keyof T ? T[K] : K extends keyof U ? U[K] : never;
}, Exclude<keyof T | keyof U, keyof T & keyof U>>

IsNever

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

// 以下是错误答案
type IsNever2<T> = T extends never ? true : false
type Test = IsNever2<never> // never

注意点:

  1. never不能触发类型分发,否则结果是never

isUnion

type IsUnion<T, Copy = T> = [T] extends [never] ? false : T extends Copy ? [Copy] extends [T] ? false : true : never

思路:先对T进行类型分发,然后再用T的完整拷贝UT进行比较来判断是否为联合类型

Remove Index Signature

type RemoveIndexSignature<T> = {
[K in keyof T as string extends K ? never : number extends K ? never : symbol extends K ? never : K]: T[K]
}

思路

  1. 问题的本质是对象的过滤。我们通过as子句的特性来实现该功能,当asnever后就会自动过滤该键

DropChar

type DropChar<S, C> = S extends `${infer L}${infer R}` ?  L extends C ?  DropChar<R, C> : `${L}${DropChar<R, C>}` : S;

MinusOne

TODO

PickByType

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

StartsWith

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

type EndsWith<T extends string, U extends string> = T extends `${infer L}${U}` ? L : false;

PartialByKeys

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

注意点

  1. 通过Merge实现{ name: string } & { age: number}{ name: string; age: number}的转化
  2. 此处的T类型为string | number | symbol,为了让它满足keyof T有两种思路,一是通过T extends keyof T ? T : never,一种是这里的T & keyof T

ObjectEntries

type ObjectEntries<T> = {
[K in keyof T]-?: [K, T[K] extends infer R | undefined ? R : T[K]]
}[keyof T]

Tuple to Nested Object

type TupleToNestedObject<T, U> = T extends [infer L, ...infer R]  ? {
[K in L & keyof any]: TupleToNestedObject<R, U>
} : U;

Reverse

type Reverse<T, Result extends readonly any[] = []> = T extends [infer L, ...infer R] ? Reverse<R, [L, ...Result]> : Result;

Flip Arguments

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

BEM style string

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

InorderTraversal

type InorderTraversal<T extends TreeNode | null> = [T] extends [TreeNode] ? (
[
...InorderTraversal<T['left']>,
T['val'],
...InorderTraversal<T['right']>,
]
) : []

Filp

type Flip<T> = {
[K in keyof T as `${T[K] & (number | string | boolean) }` ]: K
}