Ts 工具类实现(持续更新中...)

528 人阅读8 分钟阅读

简单? 不简单!

blog-header-cover

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 property
todo.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]>; // 3
type head4 = First<[() => 123, { a: string }]>; // () => 123
type head5 = First<[]>; // never
type head6 = First<[null]>; // null
type 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 4
type spaceXLength = Length<typeof spaceX>; // expected 5

Exclude

type MyExclude<T, U> = T extends U ? never : T
MyExclude<'a' | 'b' | 'c', 'a'> // 'a' | 'b'
MyExclude<'a' | 'b' | 'c', 'a' | 'b'> // 'c'

MyAwaited

type MyAwaited<T> = T extends Promise<infer A> ? MyAwaited<A> : T
type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
MyAwaited<X> // string
MyAwaited<Y> // { field: number }
MyAwaited<Z> // string | number

If

type If<C extends boolean, T, F> = C extends true ? T : F
If<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 : false
type Includes<T extends readonly any[], U> = T extends [infer P, ...infer R] ?
IsEqual<U, P> extends true ? true : Includes<R, U>
: false
Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'> // true
Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // false
Includes<[1, 2, 3, 5, 6, 7], 7> // true
Includes<[1, 2, 3, 5, 6, 7], 4> // false
Includes<[1, 2, 3], 2> // true
Includes<[1, 2, 3], 1> // true
Includes<[{}], { a: 'A' }> // false
Includes<[boolean, 2, 3, 5, 6, 7], false> // false
Includes<[true, 2, 3, 5, 6, 7], boolean> // false
Includes<[false, 2, 3, 5, 6, 7], false> // true
Includes<[{ a: 'A' }], { readonly a: 'A' }> // false
Includes<[{ readonly a: 'A' }], { a: 'A' }> // false
Includes<[1], 1 | 2> // false
Includes<[1 | 2], 1> // false
Includes<[null], undefined> // false
Includes<[undefined], null> // false

Push

type Push<T extends unknown[], U> = [...T, U]
type Push<T, U> = T extends [...infer P] ? [...P, U] : never
Push<[], 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] : never
Unshift<[], 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 : never
const fn = (v: boolean) => v ? 1 : 2
const fn1 = (v: boolean, w: any) => v ? 1 : 2
MyReturnType<() => string> // string
MyReturnType<() => 123>> // 12
MyReturnType<() => ComplexObject>> // ComplexObject
MyReturnType<() => Promise<boolean>> // Promise<boolean>
MyReturnType<() => () => 'foo'> // () => 'foo'
MyReturnType<typeof fn> // 1 | 2
MyReturnType<typeof fn1> // 1 | 2

Omit

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> = MyPick<T, MyExclude<keyof T, K>>
interface Todo {
title: string
description: string
completed: 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' | true
type b = TupleToUnion<[123]>; // 123

Chainable

基本思路:
  1. 返回自身.
  2. 使用option的时候会判断key是不是存在当前对象, 如果存在就赋值成never, 不存在则添加到对象上: K extends keyof O ? never: K.
  3. 添加的对象会进行类型合并: 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

  1. 通过展开运算符..., 获取最后一项.
  2. 通过数组 length, 读取最后一项.
  3. 通过递归, 每次排除第一项,读取最后一项.
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

  1. 使用extendsinfer来判断类型
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

  1. 使用模版字符串判断首位字符是不是' '\n\t.
  2. 使用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

  1. 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

  1. 使用extendsinfer来截取字符串的首个字符.
  2. 使用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

  1. 使用extendsinfer进行模版字符串的判断.
  2. 将字符串分成三份: A、From、B.
  3. 如果字符串满足${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

  1. 参考Replace实现.
  2. 添加一个递归处理.
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

  1. 使用extendsinfer推断出参数以及返回值.
  2. 使用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

  1. 使用递归将字符串转化为数组.
  2. 返回数组的长度.
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

  1. 使用递归的操作迭代每一个元素.
  2. 对每一个元素进行判断, 是否需要进一步扁平.
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

  1. 使用keyof T | U来进行循环.
  2. 使用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

  1. 使用递归的方式每次进行判断然后添加到结果字符串中.
  2. 使用模版字符串进行判断-${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

  1. 第一种: 将字符串转化为数组, 再通过T[number]转化.
  2. 第二种: 通过字符串模版、递归的方式每次提取一个字符串进行 union, 直到最后 union never.
  3. 第三种: 和第一种差不多, 区别在于这是每次递归的时候就使用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

  1. 找出两个对象的不同 key 数组(核心).
  2. 不同 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

  1. 使用Falsy来确定哪一些是假值.
  2. 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: string
flag: number
}
type NodeB = {
type: 'B'
id: number
flag: number
}
type NodeC = {
type: 'C'
name: string
flag: number
}
type ReplacedNodeA = {
type: 'A'
name: number
flag: string
}
type ReplacedNodeB = {
type: 'B'
id: number
flag: string
}
type ReplacedNodeC = {
type: 'C'
name: number
flag: string
}
type NoNameNodeA = {
type: 'A'
flag: number
name: never
}
type NoNameNodeC = {
type: 'C'
flag: number
name: never
}
type Nodes = NodeA | NodeB | NodeC
type ReplacedNodes = ReplacedNodeA | ReplacedNodeB | ReplacedNodeC
type NodesNoName = NoNameNodeA | NoNameNodeC | NodeB
type 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

  1. 匹配左边的符号位.
  2. 匹配中间的数据位.
  3. 匹配右边的%.
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>>,