Ts 工具类实现(持续更新中...)
528 人阅读8 分钟阅读
简单? 不简单!
简单? 不简单!

Pick
type MyPick<T, K extends keyof T> = {[P in K]: T[P];};interface Todo {title: string;description: string;completed: boolean;}type TodoPreview = MyPick<Todo, 'title' | 'completed'>;const todo: TodoPreview = {title: 'Clean room',completed: false,};
Readonly
type MyReadonly<T> = {readonly [K in keyof T]: T[K];};interface Todo {title: string;description: string;}const todo: MyReadonly<Todo> = {title: 'Hey',description: 'foobar',};todo.title = 'Hello'; // Error: cannot reassign a readonly propertytodo.description = 'barFoo'; // Error: cannot reassign a readonly property
Tuple
type TupleToObject<T extends readonly any[]> = {[K in T[number]]: K;};const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const;type result = TupleToObject<typeof tuple>; // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
First of Array
type FirstObjProperty<T extends any[]> = T extends { 0: infer A } ? A : never;type FirstWithEmptyArray<T extends any[]> = T extends [] ? never : T[0];type First<T extends any[]> = T extends [infer First, ...infer _Rest]? First: never;type First<T extends any[]> = T[number] extends never ? never : T[0];type First<T extends any[]> = T extends never[] ? never : T[0];type head1 = First<['a', 'b', 'c']>; // 'a'type head3 = First<[3, 2, 1]>; // 3type head4 = First<[() => 123, { a: string }]>; // () => 123type head5 = First<[]>; // nevertype head6 = First<[null]>; // nulltype head7 = First<[undefined]>; // undefined
Length of Tuple
type Length<T extends any[]> = T['length'];const tesla = ['tesla', 'model 3', 'model X', 'model Y'] as const;const spaceX = ['FALCON 9','FALCON HEAVY','DRAGON','STARSHIP','HUMAN SPACEFLIGHT',] as const;type teslaLength = Length<typeof tesla>; // expected 4type spaceXLength = Length<typeof spaceX>; // expected 5
Exclude
type MyExclude<T, U> = T extends U ? never : TMyExclude<'a' | 'b' | 'c', 'a'> // 'a' | 'b'MyExclude<'a' | 'b' | 'c', 'a' | 'b'> // 'c'
MyAwaited
type MyAwaited<T> = T extends Promise<infer A> ? MyAwaited<A> : Ttype X = Promise<string>type Y = Promise<{ field: number }>type Z = Promise<Promise<string | number>>MyAwaited<X> // stringMyAwaited<Y> // { field: number }MyAwaited<Z> // string | number
If
type If<C extends boolean, T, F> = C extends true ? T : FIf<true, 'a', 'b'> // 'a'If<false, 'a', 2> // 2
Concat
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]Concat<[], []> // []Concat<[], [1]> // [1]Concat<[1, 2], [3, 4]> // [1, 2, 3, 4]Concat<['1', 2, '3'], [false, boolean, '4']> // ['1', 2, '3', false, boolean, '4']
Includes
type IsEqual<X, Y> = (<T>() => T extends X ? true : false) extends (<T>() => T extends Y ? true : false) ? true : falsetype Includes<T extends readonly any[], U> = T extends [infer P, ...infer R] ?IsEqual<U, P> extends true ? true : Includes<R, U>: falseIncludes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'> // trueIncludes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // falseIncludes<[1, 2, 3, 5, 6, 7], 7> // trueIncludes<[1, 2, 3, 5, 6, 7], 4> // falseIncludes<[1, 2, 3], 2> // trueIncludes<[1, 2, 3], 1> // trueIncludes<[{}], { a: 'A' }> // falseIncludes<[boolean, 2, 3, 5, 6, 7], false> // falseIncludes<[true, 2, 3, 5, 6, 7], boolean> // falseIncludes<[false, 2, 3, 5, 6, 7], false> // trueIncludes<[{ a: 'A' }], { readonly a: 'A' }> // falseIncludes<[{ readonly a: 'A' }], { a: 'A' }> // falseIncludes<[1], 1 | 2> // falseIncludes<[1 | 2], 1> // falseIncludes<[null], undefined> // falseIncludes<[undefined], null> // false
Push
type Push<T extends unknown[], U> = [...T, U]type Push<T, U> = T extends [...infer P] ? [...P, U] : neverPush<[], 1> // [1]Push<[1, 2], '3'> // [1, 2, '3']Push<['1', 2, '3'], boolean> // ['1', 2, '3', boolean]
Unshift
type Unshift<T extends unknown[], U> = [U, ...T]type Unshift<T, U> = T extends [...infer P] ? [U, ...P] : neverUnshift<[], 1> // [1]Unshift<[1, 2], 0> // [0, 1, 2 ]Unshift<['1', 2, '3'], boolean> // [boolean, '1', 2, '3']
MyParameters
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;const foo = (arg1: string, arg2: number): void => {};const bar = (arg1: boolean, arg2: { a: 'A' }): void => {};const baz = (): void => {};type cases = [Expect<Equal<MyParameters<typeof foo>, [string, number]>>,Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>,Expect<Equal<MyParameters<typeof baz>, []>>];
MyReturnType
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : neverconst fn = (v: boolean) => v ? 1 : 2const fn1 = (v: boolean, w: any) => v ? 1 : 2MyReturnType<() => string> // stringMyReturnType<() => 123>> // 12MyReturnType<() => ComplexObject>> // ComplexObjectMyReturnType<() => Promise<boolean>> // Promise<boolean>MyReturnType<() => () => 'foo'> // () => 'foo'MyReturnType<typeof fn> // 1 | 2MyReturnType<typeof fn1> // 1 | 2
Omit
type MyExclude<T, K> = T extends K ? never : Ttype MyPick<T, K extends keyof T> = {[P in K]: T[P]}type MyOmit<T, K> = MyPick<T, MyExclude<keyof T, K>>interface Todo {title: stringdescription: stringcompleted: boolean}MyOmit<Todo, 'description'> // { title: string; completed: string }MyOmit<Todo, 'description' | 'completed'> // { title: string }
MyReadonly2
type MyExclude<T, K> = T extends K ? never : T;type MyReadonly2<T, K extends keyof T = keyof T> = {readonly [P in K]: T[P];} & {[P in MyExclude<keyof T, K>]: T[P];};// 优化后type MyReadonly<T> = {readonly [K in keyof T]: T[K];};type MyExclude<T, K> = T extends K ? never : T;type MyPick<T, K extends keyof T> = {[P in K]: T[P];};type MyOmit<T, K extends keyof T> = {[P in MyExclude<keyof T, K>]: T[P];};type MyReadonly2<T, K extends keyof T = keyof T> = MyReadonly<MyPick<T, K>> &MyOmit<T, K>;type A = MyReadonly2<Todo1>; // Readonly<Todo1>type B = MyReadonly2<Todo1, 'title' | 'description'>;// {// readonly title: string;// readonly description?: string | undefined;// completed: boolean;// }type C = MyReadonly2<Todo2, 'title' | 'description'>;// {// readonly title: string;// readonly description?: string | undefined;// completed: boolean;// }interface Todo1 {title: string;description?: string;completed: boolean;}interface Todo2 {readonly title: string;description?: string;completed: boolean;}interface Expected {readonly title: string;readonly description?: string;completed: boolean;}
DeepReadonly
type DeepReadonly2<T> = {readonly [K in keyof T]: keyof T[K] extends undefined? T[K]: DeepReadonly<T[K]>;};type DeepReadonly<T> = {readonly [K in keyof T]: T[K] extends Record<string, unknown> | Array<unknown>? DeepReadonly<T[K]>: T[K];};type X = {a: () => 22;b: string;c: {d: boolean;e: {g: {h: {i: true;j: 'string';};k: 'hello';};l: ['hi',{m: ['hey'];}];};};};type Expected = {readonly a: () => 22;readonly b: string;readonly c: {readonly d: boolean;readonly e: {readonly g: {readonly h: {readonly i: true;readonly j: 'string';};readonly k: 'hello';};readonly l: readonly ['hi',{readonly m: readonly ['hey'];}];};};};
TupleToUnion
type TupleToUnion<T extends any[]> = T[number];type TupleToUnion<T extends any[]> = T extends [infer First,infer Second,...infer Rest]? TupleToUnion<[First | Second, ...Rest]>: T[0];type a = TupleToUnion<[123, '456', true]>; // 123 | '456' | truetype b = TupleToUnion<[123]>; // 123
Chainable
基本思路:
- 返回自身.
- 使用
option
的时候会判断key
是不是存在当前对象, 如果存在就赋值成never
, 不存在则添加到对象上:K extends keyof O ? never: K
. - 添加的对象会进行类型合并:
Chainable<O & Record<K, V>>
.
type Chainable<O = {}> = {option<K extends string, V>(key: K extends keyof O ? never : K,value: V): Chainable<O & { [P in K]: V }>;get(): O;};
使用
Record
简化type Chainable<O = {}> = {option<K extends string, V>(key: K extends keyof O ? never : K,value: V): Chainable<O & Record<K, V>>;get(): O;};declare const a: Chainable;const result1 = a.option('foo', 123).option('bar', { value: 'Hello World' }).option('name', 'type-challenges').get();const result2 = a.option('name', 'another name')// @ts-expect-error.option('name', 'last name').get();type cases = [Expect<Alike<typeof result1, Expected1>>,Expect<Alike<typeof result2, Expected2>>];type Expected1 = {foo: number;bar: {value: string;};name: string;};type Expected2 = {name: string;};
Last
- 通过展开运算符
...
, 获取最后一项. - 通过数组 length, 读取最后一项.
- 通过递归, 每次排除第一项,读取最后一项.
type Last<T extends any[]> = T extends [...infer Rest, infer V] ? V : undefined;type Last<T extends any[]> = [undefined, ...T][T['length']];type Last<T extends any[]> = T extends [infer End]? End: T extends [infer First, ...infer Rest]? Last<Rest>: undefined;type Last<T extends any[]> = T extends [infer First, ...infer Rest]? T[Rest['length']]: undefined;/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<Last<[3, 2, 1]>, 1>>,Expect<Equal<Last<[() => 123, { a: string }]>, { a: string }>>];
Pop
type Pop<T extends unknown[]> = T extends [...infer R, infer L] ? R : [];
Shift
type Shift<T extends unknown[]> = T extends [infer L, ...infer R] ? L : [];
Unshift
type Unshift<T extends unknown[], V> = [V, ...T];
PromiseAll
- 使用
extends
、infer
来判断类型
declare function PromiseAll<T extends unknown[]>(values: readonly [...T]): Promise<{ [K in keyof T]: T[K] extends Promise<infer V> ? V : T[K] }>;import type { Equal, Expect } from '@type-challenges/utils';const promiseAllTest1 = PromiseAll([1, 2, 3] as const);const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const);const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)]);type cases = [Expect<Equal<typeof promiseAllTest1, Promise<[1, 2, 3]>>>,Expect<Equal<typeof promiseAllTest2, Promise<[1, 2, number]>>>,Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>];
LookUp
type LookUp<U, T> = U extends { type: T } ? U : never;/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';interface Cat {type: 'cat';breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal';}interface Dog {type: 'dog';breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer';color: 'brown' | 'white' | 'black';}type Animal = Cat | Dog;type a = LookUp<Animal, 'dog'>;type cases = [Expect<Equal<LookUp<Animal, 'dog'>, Dog>>,Expect<Equal<LookUp<Animal, 'cat'>, Cat>>];
TrimLeft
- 使用模版字符串判断首位字符是不是
' '
、\n
、\t
. - 使用
infer Rest
提取剩下的字符进行递归处理.
type TrimLeft<S extends string> = S extends `${' ' | '\n' | '\t'}${infer Rest}`? TrimLeft<Rest>: S;/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<TrimLeft<'str'>, 'str'>>,Expect<Equal<TrimLeft<' str'>, 'str'>>,Expect<Equal<TrimLeft<' str'>, 'str'>>,Expect<Equal<TrimLeft<' str '>, 'str '>>,Expect<Equal<TrimLeft<' \n\t foo bar '>, 'foo bar '>>,Expect<Equal<TrimLeft<''>, ''>>,Expect<Equal<TrimLeft<' \n\t'>, ''>>];
Trim
- 先
TrimLeft
左边空格、再TrimRight
右边空格.
type TrimLeft<S extends string> = S extends `${' ' | '\n' | '\t'}${infer R}`? TrimLeft<R>: S;type TrimRight<S extends string> = S extends `${infer R}${' ' | '\n' | '\t'}`? TrimRight<R>: S;type Trim<S extends string> = TrimLeft<TrimRight<S>>;type Trim<S extends string> = S extends| `${' ' | '\n' | '\t'}${infer R}`| `${infer R}${' ' | '\n' | '\t'}`? Trim<R>: S;/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<Trim<'str'>, 'str'>>,Expect<Equal<Trim<' str'>, 'str'>>,Expect<Equal<Trim<' str'>, 'str'>>,Expect<Equal<Trim<'str '>, 'str'>>,Expect<Equal<Trim<' str '>, 'str'>>,Expect<Equal<Trim<' \n\t foo bar \t'>, 'foo bar'>>,Expect<Equal<Trim<''>, ''>>,Expect<Equal<Trim<' \n\t '>, ''>>];
Capitalize
- 使用
extends
、infer
来截取字符串的首个字符. - 使用
UpperCase
范型来首字母大写.
type MyCapitalize<S extends string> = S extends `${infer F}${infer Rest}`? `${Uppercase<F>}${Rest}`: S;import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<MyCapitalize<'foobar'>, 'Foobar'>>,Expect<Equal<MyCapitalize<'FOOBAR'>, 'FOOBAR'>>,Expect<Equal<MyCapitalize<'foo bar'>, 'Foo bar'>>,Expect<Equal<MyCapitalize<''>, ''>>,Expect<Equal<MyCapitalize<'a'>, 'A'>>,Expect<Equal<MyCapitalize<'b'>, 'B'>>,Expect<Equal<MyCapitalize<'c'>, 'C'>>,Expect<Equal<MyCapitalize<'d'>, 'D'>>,Expect<Equal<MyCapitalize<'e'>, 'E'>>,Expect<Equal<MyCapitalize<'f'>, 'F'>>,Expect<Equal<MyCapitalize<'g'>, 'G'>>,Expect<Equal<MyCapitalize<'h'>, 'H'>>,Expect<Equal<MyCapitalize<'i'>, 'I'>>,Expect<Equal<MyCapitalize<'j'>, 'J'>>,Expect<Equal<MyCapitalize<'k'>, 'K'>>,Expect<Equal<MyCapitalize<'l'>, 'L'>>,Expect<Equal<MyCapitalize<'m'>, 'M'>>,Expect<Equal<MyCapitalize<'n'>, 'N'>>,Expect<Equal<MyCapitalize<'o'>, 'O'>>,Expect<Equal<MyCapitalize<'p'>, 'P'>>,Expect<Equal<MyCapitalize<'q'>, 'Q'>>,Expect<Equal<MyCapitalize<'r'>, 'R'>>,Expect<Equal<MyCapitalize<'s'>, 'S'>>,Expect<Equal<MyCapitalize<'t'>, 'T'>>,Expect<Equal<MyCapitalize<'u'>, 'U'>>,Expect<Equal<MyCapitalize<'v'>, 'V'>>,Expect<Equal<MyCapitalize<'w'>, 'W'>>,Expect<Equal<MyCapitalize<'x'>, 'X'>>,Expect<Equal<MyCapitalize<'y'>, 'Y'>>,Expect<Equal<MyCapitalize<'z'>, 'Z'>>];
Replace
- 使用
extends
、infer
进行模版字符串的判断. - 将字符串分成三份: A、From、B.
- 如果字符串满足
${A}${From}${B}
的话就替换成${A}${To}${B}
.
type Replace<S extends string,From extends string,To extends string> = From extends ''? S: S extends `${infer A}${From}${infer B}`? `${A}${To}${B}`: S;import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<Replace<'foobar', 'bar', 'foo'>, 'foofoo'>>,Expect<Equal<Replace<'foobarbar', 'bar', 'foo'>, 'foofoobar'>>,Expect<Equal<ReplaceAll<'foobarbar', 'bar', 'foo'>, 'foofoofoo'>>,Expect<Equal<Replace<'foobarbar', '', 'foo'>, 'foobarbar'>>,Expect<Equal<Replace<'foobarbar', 'bar', ''>, 'foobar'>>,Expect<Equal<Replace<'foobarbar', 'bra', 'foo'>, 'foobarbar'>>,Expect<Equal<Replace<'', '', ''>, ''>>];
ReplaceAll
- 参考
Replace
实现. - 添加一个递归处理.
type ReplaceAll<S extends string,From extends string,To extends string> = From extends ''? S: S extends `${infer A}${From}${infer B}`? `${A}${To}${ReplaceAll<B, From, To>}`: S;type ReplaceAll<S extends string,From extends string,To extends string> = From extends ''? S: S extends `${infer A}${From}${infer B}`? `${ReplaceAll<A, From, To>}${To}${ReplaceAll<B, From, To>}`: S;/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<ReplaceAll<'foobar', 'bar', 'foo'>, 'foofoo'>>,Expect<Equal<ReplaceAll<'foobar', 'bag', 'foo'>, 'foobar'>>,Expect<Equal<ReplaceAll<'foobarbar', 'bar', 'foo'>, 'foofoofoo'>>,Expect<Equal<ReplaceAll<'t y p e s', ' ', ''>, 'types'>>,Expect<Equal<ReplaceAll<'foobarbar', '', 'foo'>, 'foobarbar'>>,Expect<Equal<ReplaceAll<'barfoo', 'bar', 'foo'>, 'foofoo'>>,Expect<Equal<ReplaceAll<'foobarfoobar', 'ob', 'b'>, 'fobarfobar'>>,Expect<Equal<ReplaceAll<'foboorfoboar', 'bo', 'b'>, 'foborfobar'>>,Expect<Equal<ReplaceAll<'', '', ''>, ''>>];
AppendArgument
- 使用
extends
、infer
推断出参数以及返回值. - 使用
Pushed
工具函数添加参数.
type Pushed<T extends unknown[], R> = [...T, R];type AppendArgument<Fn, A> = Fn extends (...args: infer P) => infer R? (...params: Pushed<P, A>) => R: never;/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type Case1 = AppendArgument<(a: number, b: string) => number, boolean>;type Result1 = (a: number, b: string, x: boolean) => number;type Case2 = AppendArgument<() => void, undefined>;type Result2 = (x: undefined) => void;type cases = [Expect<Equal<Case1, Result1>>, Expect<Equal<Case2, Result2>>];
LengthOfString
- 使用递归将字符串转化为数组.
- 返回数组的长度.
type LengthOfString<S extends string,T extends unknown[] = []> = S extends `${infer A}${infer R}`? LengthOfString<R, [...T, A]>: T['length'];/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<LengthOfString<''>, 0>>,Expect<Equal<LengthOfString<'kumiko'>, 6>>,Expect<Equal<LengthOfString<'reina'>, 5>>,Expect<Equal<LengthOfString<'Sound! Euphonium'>, 16>>];
Flatten
- 使用递归的操作迭代每一个元素.
- 对每一个元素进行判断, 是否需要进一步扁平.
type Flatten<T extends unknown[]> = T extends [infer First, ...infer Rest]? First extends unknown[]? [...Flatten<First>, ...Flatten<Rest>]: [First, ...Flatten<Rest>]: T;/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<Flatten<[]>, []>>,Expect<Equal<Flatten<[1, 2, 3, 4]>, [1, 2, 3, 4]>>,Expect<Equal<Flatten<[[1], [2]]>, [1, 2]>>,Expect<Equal<Flatten<[1, 2, [3, 4], [[[5]]]]>, [1, 2, 3, 4, 5]>>,Expect<Equal<Flatten<[{ foo: 'bar'; 2: 10 }, 'foobar']>,[{ foo: 'bar'; 2: 10 }, 'foobar']>>];
AppendToObject
- 使用
keyof T | U
来进行循环. - 使用
extends keyof T
判断是 T 的属性还是需要添加的属性.
type AppendToObject<T, U extends string | number | symbol, V> = {[P in keyof T | U]: P extends keyof T ? T[P] : V;};/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type test1 = {key: 'cat';value: 'green';};type testExpect1 = {key: 'cat';value: 'green';home: boolean;};type test2 = {key: 'dog' | undefined;value: 'white';sun: true;};type testExpect2 = {key: 'dog' | undefined;value: 'white';sun: true;home: 1;};type test3 = {key: 'cow';value: 'yellow';sun: false;};type testExpect3 = {key: 'cow';value: 'yellow';sun: false;isMotherRussia: false | undefined;};type cases = [Expect<Equal<AppendToObject<test1, 'home', boolean>, testExpect1>>,Expect<Equal<AppendToObject<test2, 'home', 1>, testExpect2>>,Expect<Equal<AppendToObject<test3, 'isMotherRussia', false | undefined>,testExpect3>>];
Absolute
- 使用递归的方式每次进行判断然后添加到结果字符串中.
- 使用模版字符串进行判断
-${infer V}
的方式进行判断和截取.
type Absolute<T extends number | string | bigint,L extends string = ''> = `${T}` extends `${infer A}${infer Rest}`? A extends '-'? Absolute<Rest, L>: Absolute<Rest, `${L}${A}`>: L;type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer V}`? V: `${T}`;/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<Absolute<0>, '0'>>,Expect<Equal<Absolute<-0>, '0'>>,Expect<Equal<Absolute<10>, '10'>>,Expect<Equal<Absolute<-5>, '5'>>,Expect<Equal<Absolute<'0'>, '0'>>,Expect<Equal<Absolute<'-0'>, '0'>>,Expect<Equal<Absolute<'10'>, '10'>>,Expect<Equal<Absolute<'-5'>, '5'>>,Expect<Equal<Absolute<-1_000_000n>, '1000000'>>,Expect<Equal<Absolute<9_999n>, '9999'>>];
StringToUnion
- 第一种: 将字符串转化为数组, 再通过
T[number]
转化. - 第二种: 通过字符串模版、递归的方式每次提取一个字符串进行 union, 直到最后 union never.
- 第三种: 和第一种差不多, 区别在于这是每次递归的时候就使用
T[number]
转化出来了, 在进行最终的合并, 而第一种是把所有的元素放到数组里, 最后在进行T[number]
.
type StringToArr<T extends string,L extends string[] = []> = T extends `${infer A}${infer R}` ? StringToArr<R, [...L, A]> : L;type StringToUnion<T extends string> = T extends ''? never: StringToArr<T>[number];type StringToUnion<T extends string,L = never> = T extends `${infer A}${infer B}` ? A | StringToUnion<B> : never;type StringToUnion<T extends string> = T extends `${infer L}${infer R}`? [L, StringToUnion<R>][number]: never;/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<StringToUnion<''>, never>>,Expect<Equal<StringToUnion<'t'>, 't'>>,Expect<Equal<StringToUnion<'hello'>, 'h' | 'e' | 'l' | 'l' | 'o'>>,Expect<Equal<StringToUnion<'coronavirus'>,'c' | 'o' | 'r' | 'o' | 'n' | 'a' | 'v' | 'i' | 'r' | 'u' | 's'>>];
Diff
- 找出两个对象的不同 key 数组(核心).
- 不同 key 数组遍历组装成对象.
type Filter<A, B, K = A | B> = K extends A & B ? never : K;type Diff<O, O1> = {[P in Filter<keyof O, keyof O1>]: P extends keyof O? O[P]: P extends keyof O1? O1[P]: never;};type Diff<O, O1> = {[P in Exclude<keyof O | keyof O1, keyof O & keyof O1>]: P extends keyof O? O[P]: P extends keyof O1? O1[P]: never;};type Diff<O, O1> = {[P in keyof Omit<O, keyof O1> | keyof Omit<O1, keyof O>]: P extends keyof O? O[P]: P extends keyof O1? O1[P]: never;};/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type Foo = {name: string;age: string;};type Bar = {name: string;age: string;gender: number;};type Coo = {name: string;gender: number;};type cases = [Expect<Equal<Diff<Foo, Bar>, { gender: number }>>,Expect<Equal<Diff<Bar, Foo>, { gender: number }>>,Expect<Equal<Diff<Foo, Coo>, { age: string; gender: number }>>,Expect<Equal<Diff<Coo, Foo>, { age: string; gender: number }>>];
AnyOf
- 使用
Falsy
来确定哪一些是假值. Record<PropertyKey, never>
表示一个空对象.
type Falsy = 0 | '' | false | [] | Record<PropertyKey, never>;type AnyOf<T extends readonly any[]> = T extends Array<Falsy> ? false : true;/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<AnyOf<[1, 'test', true, [1], { name: 'test' }, { 1: 'test' }]>, true>>,Expect<Equal<AnyOf<[1, '', false, [], {}]>, true>>,Expect<Equal<AnyOf<[0, 'test', false, [], {}]>, true>>,Expect<Equal<AnyOf<[0, '', true, [], {}]>, true>>,Expect<Equal<AnyOf<[0, '', false, [1], {}]>, true>>,Expect<Equal<AnyOf<[0, '', false, [], { name: 'test' }]>, true>>,Expect<Equal<AnyOf<[0, '', false, [], { 1: 'test' }]>, true>>,Expect<Equal<AnyOf<[0, '', false, [], { name: 'test' }, { 1: 'test' }]>, true>>,Expect<Equal<AnyOf<[0, '', false, [], {}]>, false>>,Expect<Equal<AnyOf<[]>, false>>];
ReplaceKeys
type ReplaceKeys<U, T, Y> = {[K in keyof U]: K extends T ? K extends keyof Y ? Y[K] : never : U[K]}/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils'type NodeA = {type: 'A'name: stringflag: number}type NodeB = {type: 'B'id: numberflag: number}type NodeC = {type: 'C'name: stringflag: number}type ReplacedNodeA = {type: 'A'name: numberflag: string}type ReplacedNodeB = {type: 'B'id: numberflag: string}type ReplacedNodeC = {type: 'C'name: numberflag: string}type NoNameNodeA = {type: 'A'flag: numbername: never}type NoNameNodeC = {type: 'C'flag: numbername: never}type Nodes = NodeA | NodeB | NodeCtype ReplacedNodes = ReplacedNodeA | ReplacedNodeB | ReplacedNodeCtype NodesNoName = NoNameNodeA | NoNameNodeC | NodeBtype cases = [Expect<Equal<ReplaceKeys<Nodes, 'name' | 'flag', { name: number; flag: string }>, ReplacedNodes>>,Expect<Equal<ReplaceKeys<Nodes, 'name', { aa: number }>, NodesNoName>>,]
IsNever
为什么不使用
T extends never
?T extends never
是分布式条件类型. 最后的结果条件类型分布在联合上. 如果 T 是一个联合 ExtendsNever 应用于联合的每个成员, 结果是所有应用程序的联合ExtendsNever<'a' | 'b'> == ExtendsNever<'a' > | ExtendsNever<'b'>
。never 是空集合. 它在 union 中的行为暗示了这一点 'a' | never == 'a'
. 因此, 在分配时 never, T extends never
永远不会应用, 因为此联合中没有成员, 因此结果是never
.type IsNever<T> = T[] extends never[] ? true : false;type IsNever<T> = [T] extends [never] ? true : false;type IsNever<T> = { K: T } extends { K: never } ? true : false;/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type cases = [Expect<Equal<IsNever<never>, true>>,Expect<Equal<IsNever<never | string>, false>>,Expect<Equal<IsNever<''>, false>>,Expect<Equal<IsNever<undefined>, false>>,Expect<Equal<IsNever<null>, false>>,Expect<Equal<IsNever<[]>, false>>,Expect<Equal<IsNever<{}>, false>>];
RemoveIndexSignature
type RemoveIndexSignature<T> = {[P in keyof T as number extends P? never: string extends P? never: symbol extends P? never: P]: T[P];};/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type Foo = {[key: string]: any;foo(): void;};type Bar = {[key: number]: any;bar(): void;0: string;};const foobar = Symbol('foobar');type FooBar = {[key: symbol]: any;[foobar](): void;};type Baz = {bar(): void;baz: string;};type cases = [Expect<Equal<RemoveIndexSignature<Foo>, { foo(): void }>>,Expect<Equal<RemoveIndexSignature<Bar>, { bar(): void; 0: string }>>,Expect<Equal<RemoveIndexSignature<FooBar>, { [foobar](): void }>>,Expect<Equal<RemoveIndexSignature<Baz>, { bar(): void; baz: string }>>];
PercentageParser
- 匹配左边的符号位.
- 匹配中间的数据位.
- 匹配右边的%.
type GetNumber<T extends string> = T extends `+${infer R}`? ['+', R]: T extends `-${infer R}`? ['-', R]: ['', T];type PercentageParser<A extends string> = A extends `${infer R}%`? [...GetNumber<R>, '%']: [...GetNumber<A>, ''];type Parse1<A extends string> = A extends `${infer T}${infer _}`? T extends '+' | '-'? T: '': '';type Parse2<A extends string> = A extends `${Parse1<A>}${infer R}${Parse3<A>}`? R: '';type Parse3<A extends string> = A extends `${infer _}${'%'}` ? '%' : '';type PercentageParser<A extends string> = [Parse1<A>, Parse2<A>, Parse3<A>];type IsSign<T> = T extends '+' | '-' ? T : never;type GetNumber<T extends string> = T extends `${IsSign<infer R>}${infer L}`? [R, L]: ['', T];type PercentageParser<T extends string> = T extends `${infer R}%`? [...GetNumber<R>, '%']: [...GetNumber<T>, ''];/* _____________ Test Cases _____________ */import type { Equal, Expect } from '@type-challenges/utils';type Case0 = ['', '', ''];type Case1 = ['+', '', ''];type Case2 = ['+', '1', ''];type Case3 = ['+', '100', ''];type Case4 = ['+', '100', '%'];type Case5 = ['', '100', '%'];type Case6 = ['-', '100', '%'];type Case7 = ['-', '100', ''];type Case8 = ['-', '1', ''];type Case9 = ['', '', '%'];type Case10 = ['', '1', ''];type Case11 = ['', '100', ''];type cases = [Expect<Equal<PercentageParser<''>, Case0>>,Expect<Equal<PercentageParser<'+'>, Case1>>,Expect<Equal<PercentageParser<'+1'>, Case2>>,Expect<Equal<PercentageParser<'+100'>, Case3>>,Expect<Equal<PercentageParser<'+100%'>, Case4>>,Expect<Equal<PercentageParser<'100%'>, Case5>>,