TypeScript 类型体操
通过学习 TypeScript 类型体操 —— Type-Challenges 来巩固所学到 TypeScript 知识点
在 Type-Challenges 中,我们可以从简单、中等、困难以及地狱难度,循序渐进的学习 TypeScript 高级技巧
Easy 简单
Pick 选取
实现内置的
Pick<T, K>
Pick 表示从一个类型 T 中选取指定的几个字段(K)组合成一个新的类型
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = MyPick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};实现:
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};K extends keyof T表示K是keyof T的子类型,确保我们在使用MyPick时只能传递类型T中已有的属性键P in K遍历类型K拿到具体属性键
type result = MyPick<Todo, "time">;
// Error: 类型“"time"”不满足约束“keyof Todo”Readonly 只读
实现内置的
Readonly<T>
Readonly 会接收一个泛型参数,并返回一个完全一样的类型,只是所有属性都会被 readonly 所修饰
interface Todo {
title: string;
description: string;
}
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar",
};
todo.title = "Hello"; // Error: 无法为“title”赋值,因为它是只读属性
todo.description = "barFoo"; // Error: 无法为“description”赋值,因为它是只读属性实现:
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};TupleToObject 元组转对象
TupleToObject<T> 是用来把一个元组转换成一个 key/value 相同的对象
const tuple = ["tesla", "model 3", "model X", "model Y"] as const;
type result = TupleToObject<typeof tuple>;
// 结果:{ tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}实现:
type TupleToObject<T extends readonly any[]> = {
[K in T[number]]: K;
};索引访问类型
在 TypeScript 中,索引访问类型(Index Types)是一种特殊的类型,它允许我们使用类型中的某个索引类型(比如字符串或数字类型)来访问另一个类型中的属性。索引访问类型通常用于动态地访问对象的属性或数组的元素类型
as const常量断言,可以将一个数组或对象的类型推断为只读的精确类型,在此处会将['tesla', 'model 3']推导为常量元组表示其不能新增、删除、修改元素(即readonly ["tesla", "model 3", "model X", "model Y"])K in T[number]- 先通过数字索引类型
T[number],获取类型T中的所有元素类型,并将它们组成一个联合类型 - 再对联合类型进行遍历拿到具体的元素
- 先通过数字索引类型
First 数组的第一个元素
First<T> 会接受一个数组 T 并返回它的第一个元素的类型
type arr1 = ["a", "b", "c"];
type arr2 = [3, 2, 1];
type head1 = First<arr1>;
// 结果:'a'
type head2 = First<arr2>;
// 结果:3实现:
/* 使用索引实现 */
type First<T extends any[]> = T extends [] ? never : T[0];
type First<T extends any[]> = T["length"] extends 0 ? never : T[0];
type First<T extends any[]> = T[0] extends T[number] ? T[0] : never;
/* 使用 infer 占位实现 */
type First<T extends any[]> = T extends [infer F, ...infer Rest] ? F : never;- 判断
T是否是一个空数组T extends []T['length'] extends 0T[0] extends T[number]
T[0]根据下标取数组第一个元素infer R表示数组第一个元素的占位...infer Rest表示数组剩余元素的占位
Length 元组的长度
Length<T> 用来获取一个数组(包括类数组)的长度
type result1 = Length<[1, 2, 3]>;
// 结果:3
type result2 = Length<{ a: "a"; length: 10 }>;
// 结果:10实现:
type Length<T extends any> = T extends { length: number } ? T["length"] : never;T extends { length: number }判断T是否为{ length: number }的子类型,如果是则代表T是数组或类数组T['length']获取T对象的length属性的值
警告
在 TypeScript 中不能使用 T.length 来取值,应使用 T['length'] 即索引访问类型
Exclude 排除
实现内置的
Exclude <T, U>
从联合类型 T 中排除 U 的类型成员(即取 T 对于 U 的差集),来构造一个新的类型
type result = MyExclude<"name" | "age" | "sex", "sex" | "address">;
// 结果:'name' | 'age'实现:
type MyExclude<T, U> = T extends U ? never : T;分布式条件类型(对联合类型应用 extends 时,会遍历联合类型成员并一一应用该条件类型)
Awaited 获取 Promise 返回值类型
实现内置的
Awaited<T>
Awaited 可以用来获取 Promise 返回值类型
type result1 = MyAwaited<Promise<string>>;
// 结果:string
type result2 = MyAwaited<Promise<string | number>>;
// 结果:string | number实现:
type MyAwaited<T> = T extends Promise<infer R> ? MyAwaited<R> : T;If 判断
If<C, T, F> 接收一个条件类型 C ,一个判断为真时的返回类型 T ,以及一个判断为假时的返回类型 F。 C 只能是 true 或者 false, T 和 F 可以是任意类型
type A = If<true, "a", "b">;
// 结果:'a'
type B = If<false, "a", "b">;
// 结果:'b'实现:
type If<C extends boolean, T, F> = C extends true ? T : F;C extends boolean表示类型C只能为boolean的子类型,即只能为true或falseC extends true表示类型C可以被赋值为字面量类型true时条件成立(相当于JavaScript中的C === true)
Concat 数组的 concat 方法
Concat<T, U> 可以将两个数组合并起来,即在类型系统里实现 JavaScript 内置的 Array.concat 方法
type result = Concat<[1, 2], [3, 4]>;
// 结果:[1, 2, 3, 4]实现:
type Concat<T extends any[], U extends any[]> = [...T, ...U];T extends any[]限制传入的参数只能是数组[...T, ...U]和ES6的扩展运算符一样
Includes 数组的 includes 方法
这题不太 Easy
Includes<T, U> 判断 U 是否在数组 T 中,即在类型系统里实现 JavaScript 的 Array.includes 方法
type result1 = Includes<[1, 2, 3], 1>;
// 结果:true
type result2 = Includes<[1, 2, 3], 4>;
// 结果:false
type result3 = Includes<[boolean, 1, 2, 3], true>;
// 结果:false
type result4 = Includes<[true, 1, 2, 3], boolean>;
// 结果:false实现:
/* 简单版(存在问题)*/
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
? 1
: 2
? true
: false;
/* 递归完善版 */
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
? 1
: 2
? true
: false;
type Includes<T extends any[], U> = T extends [infer F, ...infer Rest]
? Equal<F, U> extends true
? true
: Includes<Rest, U>
: false;建议
boolean 实际是 true | false 的别名(详细信息)
Equal是用来判断两个值是否相等的辅助方法(具体可以看这里)
Push 数组 push 方法
Push<T, U> 将 U 添加到数组 T 中,即在类型系统里实现 JavaScript 的 Array.push 方法
type result = Push<[1, 2, 3], 4>;
// 结果:[1, 2, 3, 4]实现:
type Push<T extends any[], U> = [...T, U];Unshift 数组的 unshift 方法
在类型系统里实现 JavaScript 的 Array.unshift 方法
type result = Unshift<[1, 2], 0>;
// 结果:[0, 1, 2]实现:
type Unshift<T extends any[], U> = [U, ...T];Parameters 函数的参数类型
实现内置的
Parameters<T>
Parameters 可以获取一个函数的参数类型,其返回的结果是一个元组
const foo = (arg1: string, arg2: number): void => {};
const bar = (arg1: boolean, arg2: { a: "A" }): void => {};
const baz = (): void => {};
type result1 = MyParameters<typeof foo>;
// 结果:[string, number]
type result2 = MyParameters<typeof bar>;
// 结果:[boolean, { a: 'A' }]
type result3 = MyParameters<typeof baz>;
// 结果:[]实现:
type MyParameters<T extends (...args: any[]) => any> = T extends (
...args: infer R
) => any
? R
: never;Medium 中级
ReturnType 函数返回类型
实现内置的
ReturnType<T>
const fn = (v: boolean) => (v ? 1 : 2);
type result = MyReturnType<typeof fn>;
// 结果:1 | 2实现:
type MyReturnType<T extends (...args: any[]) => any> = T extends (
...args: any[]
) => infer R
? R
: never;Omit 移除
实现内置的
Omit<T, K>
Omit 可以移除 T 类型中的指定字段
interface Todo {
title: string;
description: string;
completed: boolean;
}
type result = MyOmit<Todo, "description">;
// 结果:{ completed: boolean }实现:
可以借助在上面已经实现过的
Pick和Exclude配合来实现
type MyOmit<T, K extends keyof any> = MyPick<T, MyExclude<keyof T, K>>;MyExclude<keyof T, K>从T中移除指定字段,得到一个联合类型('title' | 'completed')即我们需要数据MyPick<T, 'title' | 'completed'>从T中选取这两个字段组成一个新的类型
Readonly 按需只读
内置的
Readonly增强版
MyReadonly<T, K>
- 当传递
K时,只对K中的属性集变成只读(按需只读) - 未传递
K时则使所有属性都变为只读
interface Todo {
title: string;
description: string;
completed?: boolean;
}
interface Expected1 {
readonly title: string;
readonly description: string;
readonly completed?: boolean;
}
interface Expected2 {
title: string;
readonly description: string;
readonly completed?: boolean;
}
type result1 = MyReadonly<Todo>;
// 结果:Expected1
type result2 = MyReadonly<Todo, "description" | "completed">;
// 结果:Expected2
const todo: MyReadonly<Todo, "title" | "description"> = {
title: "Hey",
description: "foobar",
completed: false,
};
todo.title = "Hello"; // Error
todo.description = "barFoo"; // Error
todo.completed = true; // OK实现:
type MyReadonly<T, K extends keyof T = keyof T> = Omit<T, K> & {
readonly [P in K]: T[P];
};K extends keyof T = keyof T表示K只能是T的键,同时如果没有传递K则默认为T的所有键
DeepReadonly 深度只读
DeepReadonly<T> 可以将对象的每个参数及其子对象递归地设为只读
type X = {
x: {
a: 1;
b: "hi";
};
y: "hey";
};
type result = DeepReadonly<X>;
// 结果:Expected
type Expected = {
readonly x: {
readonly a: 1;
readonly b: "hi";
};
readonly y: "hey";
};实现:
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends { [key: string]: any }
? DeepReadonly<T[P]>
: T[P];
};T[P] extends { [key: string]: any }判断T[P]是否是一个包含索引签名的对象,如果是则递归调用DeepReadonly,否则直接返回T[P]
TupleToUnion 元组转联合类型
TupleToUnion<T> 可以将元组转换为联合类型
type result = TupleToUnion<["1", "2", "3"]>;
// 结果:'1' | '2' | '3'实现:
/* 使用 T[number] */
type TupleToUnion<T extends readonly any[]> = T[number];
/* 使用 infer R */
type TupleToUnion<T> = T extends Array<infer R> ? R : never;T[number]获取类型T中的所有元素类型,并将它们组成一个联合类型T extends Array<infer R> ? R : never表示如果T是一个数组,则返回数组的每个元素,否则返回never
Chainable 链式调用
Chainable 让一个对象可以进行链式调用(提供两个函数 option(key, value) 和 get():在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果)
declare const config: Chainable<{}>;
const result = config
.option("foo", 123)
.option("name", "type-challenges")
.option("bar", { value: "Hello World" })
.get();
// 结果:
interface Expected {
foo: number;
name: string;
bar: {
value: string;
};
}实现:
type Chainable<T> = {
option<K extends string, V>(
key: K,
value: V
): Chainable<Omit<T, K> & { [P in K]: V }>;
get(): T;
};Chainable<>返回一个新的Chainable对象,使其可以递归调用,其中T为Omit<T, K> & { [P in K]: V }Omit<T, K>移除T中的指定字段K
Last 数组的最后一个元素
Last<T> 会接受一个数组 T 并返回其最后一个元素的类型
type result1 = Last<["a", "b", "c"]>;
// 结果:'c'
type result2 = Last<[3, 2, 1]>;
// 结果:1实现:
/* 使用 infer 占位实现 */
type Last<T extends any[]> = T extends [...infer Rest, infer L] ? L : never;
type Last<T extends any[]> = [any, ...T][T["length"]];T extends [...infer Rest, infer L] ? L : never表示如果T是一个数组,则返回数组的最后一个元素,否则返回never[any, ...T]构建一个新数组,第一个元素为any,其余元素为TT['length']可以获取数组T的长度
/* 以 type result1 = Last<['a', 'b', 'c']> 为例 */
// 原数组
const T = ["a", "b", "c"];
// 新数组
const arr = [any, 1, 2, 3];
// 结果:'c'
const result = arr[T["length"]];Pop 数组的 pop 方法
Pop<T> 返回一个由数组 T 的前 length-1 项以相同的顺序组成的数组
type result1 = Last<["a", "b", "c", "d"]>;
// 结果:['a', 'b', 'c']
type result2 = Last<[]>;
// 结果:[]实现:
type Pop<T extends any[]> = T extends []
? []
: T extends [...infer Rest, infer L]
? Rest
: never;- 先判断
T是否为空数组,如果是则返回空数组 - 再判断
T是否是一个数组
PromiseAll 获取 Promise.all 的返回值类型
PromiseAll 可以用来获取 Promise.all() 函数的所有返回值类型
type result = typeof PromiseAll([1, 2, Promise.resolve(3)])
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
const p = PromiseAll([promise1, promise2, promise3] as const)
// 结果:Promise<[number, 42, string]>实现:
警告
声明 PromiseAll 时,应使用 function ,而不是 type
type MyAwaited<T> = T extends Promise<infer R> ? MyAwaited<R> : T;
declare function PromiseAll<T extends any[]>(
values: readonly [...T]
): Promise<{
[K in keyof T]: MyAwaited<T[K]>;
}>;- 遍历传入的数组,获取每个元素的类型
MyAwaited<T>是之前实现的Awaited类型,用于获取Promise的返回值类型
LookUp 查找
LookUp 可以根据某个属性值在联合类型中查找类型
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">;
// 结果:Dog实现:
type LookUp<U extends { type: string }, T extends string> = U extends {
type: T;
}
? U
: never;U extends { type: string }表示U必须包含type属性T extends string表示T必须是一个字符串
TrimLeft TrimRight Trim
TrimLeft TrimRight Trim 用于移除字符串中的空白字符
type result1 = TrimLeft<" Hello World ">;
// 结果:'Hello World '
type result2 = TrimRight<" Hello World ">;
// 结果:' Hello World'
type result3 = Trim<" Hello World ">;
// 结果:'Hello World'实现:
type Space = " " | "\n" | "\t";
type TrimLeft<S extends string> = S extends `${Space}${infer R}`
? TrimLeft<R>
: S;
type TrimRight<S extends string> = S extends `${infer R}${Space}`
? TrimRight<R>
: S;
type Trim<S extends string> = S extends
| `${Space}${infer R}`
| `${infer R}${Space}`
? Trim<R>
: S;Capitalize 首字母大写和 Uncapitalize 首字母小写
实现内置的
Capitalize和Uncapitalize
Capitalize<T>将一个字符串的首字母变成大写的Uncapitalize<T>将一个字符串的首字母变成小写的
type result1 = MyCapitalize<"hello world">;
// 结果:'Hello world'
type result2 = MyUncapitalize<"Hello world">;
// 结果:'hello world'实现:
type MyCapitalize<S extends string> = S extends `${infer L}${infer R}`
? `${Uppercase<L>}${R}`
: S;
type MyUncapitalize<S extends string> = S extends `${infer L}${infer R}`
? `${Lowercase<L>}${R}`
: S;- 通过
infer获取字符串的首字母和剩余部分,然后调用内置的工具函数Uppercase或Lowercase实现首字母的大小写转换
Replace 替换
Replace<S, From, To> 将字符串 S 中的 From 替换为 To
type result = 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 L}${From}${infer R}`
? `${L}${To}${R}`
: S;From extends ''表示From为空字符串时- 通过
infer获取字符串的前缀、后缀和From的位置,然后拼接字符串
ReplaceAll 替换所有
ReplaceAll<S, From, To> 将字符串 S 中的所有 From 替换为 To
type result = ReplaceAll<"t y p e s", " ", "">;
// 结果:'types'实现:
type ReplaceAll<
S extends string,
From extends string,
To extends string
> = From extends ""
? S
: S extends `${infer L}${From}${infer R}`
? `${L}${To}${ReplaceAll<R, From, To>}`
: S;- 在
Replace的基础上,递归调用ReplaceAll实现替换所有 L不需要递归调用,因为L是前缀,不会包含From
AppendArgument 追加参数
AppendArgument<Fn, A> 将类型 A 追加到函数 Fn 的参数列表中
type Fn = (a: number, b: string) => number;
type result = AppendArgument<Fn, boolean>;
// 结果:(a: number, b: string, x: boolean) => number实现:
type AppendArgument<Fn, A> = Fn extends (...args: infer Args) => infer T
? (...args: [...Args, A]) => T
: never;- 通过
infer获取函数的参数列表和返回值类型,然后将A追加到参数列表中,最后返回一个新的函数类型
Permutation 全排列
Permutation 可以将联合类型中的每一个类型进行排列组合
type result = 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]
? []
: T extends T
? [T, ...Permutation<Exclude<K, T>>]
: never;[T] extends [never]用于判断联合类型是否已经遍历完毕(遍历完毕时返回空数组)T extends T利用分部条件类型的特性,将T作为true条件分支,never作为false条件分支[T, ...Permutation<Exclude<K, T>>]将T放在数组的第一位,然后递归调用Permutation,将T从K中移除
LengthOfString 字符串长度
LengthOfString<S> 用于计算字符串的长度
type result = LengthOfString<"qingfeng">;
// 结果:6实现:
type LengthOfString<
S extends string,
T extends string[] = []
> = S extends `${infer Char}${infer R}`
? LengthOfString<R, [...T, Char]>
: T["length"];- 通过一个数组
T来计算字符串的长度,通过条件判断进行递归调用,最后返回T的长度(即字符串的长度)T extends string[] = []用于记录字符串的每一个字符S extends ${infer Char}${infer R}通过infer获取字符串的首字符和剩余部分T['length']通过索引类型获取数组的长度
// 第一次调用
type result1 = LengthOfString<"qingfeng", []>; // T = []
// 第二次调用
type result2 = LengthOfString<"ingfeng", ["q"]>; // T = ['q']
// 第三次调用
type result3 = LengthOfString<"ngfeng", ["q", "i"]>; // T = ['q', 'i']
// 第四次调用
type result4 = LengthOfString<"gfeng", ["q", "i", "n"]>; // T = ['q', 'i', 'n']
// 第五次调用
type result5 = LengthOfString<"feng", ["q", "i", "n", "g"]>; // T = ['q', 'i', 'n', 'g']
// 第六次调用
type result6 = LengthOfString<"eng", ["q", "i", "n", "g", "f"]>; // T = ['q', 'i', 'n', 'g', 'f']
// 第七次调用
type result7 = LengthOfString<"ng", ["q", "i", "n", "g", "f", "e"]>; // T = ['q', 'i', 'n', 'g', 'f', 'e']
// 第八次调用
type result8 = LengthOfString<"g", ["q", "i", "n", "g", "f", "e", "n"]>; // T = ['q', 'i', 'n', 'g', 'f', 'e', 'n']Flatten 数组扁平化
Flatten<T> 用于将多维数组扁平化为一维数组
type result = Flatten<[1, 2, [3, 4], [[[5]]]]>;
// 结果:[1, 2, 3, 4, 5]实现:
type Flatten<T extends any[]> = T extends [infer L, ...infer R]
? L extends any[]
? [...Flatten<L>, ...Flatten<R>]
: [L, ...Flatten<R>]
: [];- 遍历数组的每一项并判断其是否为数组,如果是数组则递归调用
Flatten,否则直接返回
AppendToObject 给对象追加属性
AppendToObject 可以给对象(接口)追加一个新的属性
type result = AppendToObject<{ id: number }, "name", "qingfeng">;
// 结果:{ id: number; name: 'qingfeng' }实现:
type AppendToObject<T, K extends keyof any, V> = {
[P in keyof T | K]: P extends keyof T ? T[P] : V;
};K extends keyof any用于将K限制为string | number | symbol中的一种keyof T | K将T的所有属性和K合并为一个联合类型- 再通过条件判断,如果
P是T的属性,则返回T[P],否则返回V
Absolute 绝对值
Absolute 可以获取一个数值的绝对值(返回值类型为 string)
type result = Absolute<-1996>;
// 结果:'1996'实现:
type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer R}`
? R
: `${T}`;- 通过条件判断,如果
T是负数,则返回其绝对值,否则直接返回 - 通过 `
${T}` 将T转换为字符串类型
StringToUnion 字符串转联合类型
StringToUnion<T> 可以将字符串转换为联合类型
type result = StringToUnion<"mao">;
// 结果:'m' | 'a' | 'o'实现:
type StringToUnion<T extends string> = T extends `${infer L}${infer R}`
? L | StringToUnion<R>
: never;- 通过条件判断:如果
T不是空字符串,则将T的首字符和剩余部分分别赋值给L和R,然后递归调用StringToUnion,否则返回never
Merge 合并对象
Merge<F, S> 可以将两个对象合并为一个对象,如果有相同的属性,则使用第二个对象的属性
type result = Merge<
{
name: string;
age: string;
},
{
age: number;
sex: string;
}
>;
// 结果:{ name: string; age: number; sex: string }实现:
type Merge<F, S> = {
[P in keyof F | keyof S]: P extends keyof S
? S[P]
: P extends keyof F
? F[P]
: never;
};keyof F | keyof S将F和S的所有属性合并为一个联合类型- 先判断 P 是否是
S的属性,再判断 P 是否是F的属性
KebabCase 驼峰转短横线
KebabCase<S> 可以将驼峰形式的字符串转换为短横线形式(即 camelCase or PascalCase 转换为 kebab-case)
type result = KebabCase<"FooBarBaz">;
// 结果:'foo-bar-baz'实现:
type KebabCase<S extends string> = S extends `${infer L}${infer R}`
? R extends Uncapitalize<R>
? `${Uncapitalize<L>}${KebabCase<R>}`
: `${Uncapitalize<L>}-${KebabCase<R>}`
: S;- 先通过
infer获取字符串的首字符和剩余部分 - 通过条件判断,如果剩余部分是小写,则直接拼接,否则在首字符后面拼接
-再拼接剩余部分,同时对剩余部分递归调用KebabCase
Diff 获取两个类型的差异属性
Diff<O1, O2> 可以获取两个接口类型中的差异属性
type Foo = {
a: string;
b: number;
};
type Bar = {
a: string;
c: boolean;
};
type result = Diff<Foo, Bar>;
// 结果:{ b: number, c: boolean }实现:
type Diff<O1, O2> = Omit<O1 & O2, keyof O1 & keyof O2>;先获取 O1 和 O2 的共有属性,然后再从 O1 & O2 中移除这些共有属性
O1 & O2将O1和O2合并为一个类型keyof O1 & keyof O2获取O1和O2共有的属性名称并组成一个联合类型
AnyOf 数组元素真值判断
AnyOf<T> 可以判断数组中是否有元素为真值
type result1 = AnyOf<[1, "", false, [], {}]>;
// 结果:true
type result2 = AnyOf<[0, "", false, [], {}]>;
// 结果:false实现:
type False = 0 | "" | false | undefined | null | [] | { [key: string]: never };
type AnyOf<T extends readonly any[]> = T[number] extends False ? false : true;False是一个假值类型,包含了0、''、false、undefined、null、[]、{}等- 再通过
T[number]索引迭代,判断数组中的每个元素是否是假值类型
IsNever 是否是 Never 类型
IsNever<T> 可以判断 T 是否是 never 类型
type result1 = IsNever<never>;
// 结果:true
type result2 = IsNever<undefined>;
// 结果:false
type result3 = IsNever<null>;
// 结果:false实现:
type IsNever<T> = [T] extends [never] ? true : false;[T] extends [never]可以判断T是否是never类型
IsUnion 是否是联合类型
IsUnion<T> 可以判断 T 是否是联合类型
type result1 = IsUnion<string | number>;
// 结果:true
type result2 = IsUnion<string>;
// 结果:false实现:
type IsUnion<T, C = T> = (
T extends T ? (C extends T ? true : unknown) : never
) extends true
? false
: true;T extends T ? (C extends T ? true : unknown) : never可以判断T是否是联合类型T extends T应用分布式条件类型对T进行子类型分发C extends T判断C是否是T的子类型
/* 案例一 */
type T = string | number
// 执行 T extends T
string | number extends string | number
string extends string | number // true 走 C extends T 逻辑
// 执行 C extends T
string | number extends string // unknown
string | number extends number // unknown
unknown extends true // false
/* 案例二 */
type T = string
// 执行 T extends T
string extends string // true 走 C extends T 逻辑
// 执行 C extends T
string extends string // true
true extends true // trueReplaceKeys 类型替换
ReplaceKeys<U, T, Y> 可以将类型 U 中已经存在的类型 T 替换为类型 Y
type result1 = ReplaceKeys<
{ id: number; name: string },
"name",
{ name: boolean }
>;
// 结果:{ id: number; name: boolean }
type result2 = ReplaceKeys<
{ id: number; name: string },
"age",
{ age: number }
>;
// 结果:{ id: number; name: string }实现:
type ReplaceKeys<U, T, Y> = {
[P in keyof U]: P extends T ? (P extends keyof Y ? Y[P] : never) : U[P];
};[P in keyof U]遍历U的所有属性- 判断
P是否是T,如果是则判断P是否是Y的属性,如果是则返回Y[P],否则返回never,如果不是则返回U[P]
RemoveIndexSignature 移除索引签名
RemoveIndexSignature<T> 可以移除类型 T 中的索引签名
type result = RemoveIndexSignature<{ [key: string]: any; foo(): void }>;
// 结果:{ foo(): void }实现:
type RemoveIndexSignature<T> = {
[K in keyof T as string extends K
? never
: number extends K
? never
: symbol extends K
? never
: K]: T[K];
};- 索引签名为
string、number、symbol,所以可以通过string extends K、number extends K、symbol extends K来判断是否是索引签名 as关键字可以用来重命名属性名称,这里将索引签名的属性名称重命名为never,这样就可以移除索引签名
PercentageParser 百分比解析
PercentageParser<A> 可以将百分比字符串解析为数组
type result1 = PercentageParser<"+100%">;
// 结果:['+', '100', '%']
type result2 = PercentageParser<"-100">;
// 结果:['-', '100', '']
type result3 = PercentageParser<"100%">;
// 结果:['', '100', '%']
type result4 = PercentageParser<"100">;
// 结果:['', '100', '']实现:
type CheckPrefix<T extends string> = T extends "+" | "-" ? T : never;
type CheckSuffix<T extends string> = T extends `${infer L}%`
? [L, "%"]
: [T, ""];
type PercentageParser<A extends string> = A extends `${CheckPrefix<
infer L
>}${infer R}`
? [L, ...CheckSuffix<R>]
: ["", ...CheckSuffix<A>];CheckPrefix用来判断是否有+或-前缀,如果不存在则返回never表示没有符号CheckSuffix用来判断是否有%后缀- 存在
%时返回的数组最后一个元素是% - 不存在
%时返回的数组最后一个元素是空字符串
- 存在
