# 内置工具类型
TS 内置工具类型 (opens new window)的源码实现 (opens new window)。
# ReturnType
const fn = (v: boolean) => {
if (v) return 1;
else return 2;
};
type a = MyReturnType<typeof fn>; // should be "1 | 2"
type MyReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer P
? P
: never;
2
3
4
5
6
7
8
9
10
11
12
# Omit
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = MyOmit<Todo, "description" | "title">;
const todo: TodoPreview = { completed: false };
type MyOmit<T, K> = {
[key in keyof T as key extends K ? never : key]: T[key]
};
2
3
4
5
6
7
8
9
10
11
12
13
# Pick
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> = {
[key in K]: T[key];
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Equal
type Equal<A, B> = A extends B ? (B extends A ? true : false) : false;
type Y1 = Equal<string, string>; // true
type Y2 = Equal<string, number>; // false
type Y3 = Equal<{ name: string }, { name: string }>; // true
type Y4 = Equal<{ name: string }, { age: number }>; // false
type Y5 = Equal<{ name: string }, { name?: string }>; // false
type User1 = {
name?: string;
age: number;
address: string;
};
type User2 = {
name?: string;
} & {
age: number;
address: string;
};
type Y6 = Equal<User1, User2>; // true
type Y7 = Equal<true, boolean>; // boolean,错误
type Y8 = Equal<1 | 2, 1>; // boolean,错误
type Y9 = Equal<any, string>; // boolean,错误
type Y10 = Equal<{ name: string }, { readonly name: string }>; // true,错误
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type Equal<A, B> = [A, B] extends [B, A] ? true : false;
type Y1 = Equal<string, string>; // true
type Y2 = Equal<string, number>; // false
type Y3 = Equal<{ name: string }, { name: string }>; // true
type Y4 = Equal<{ name: string }, { age: number }>; // false
type Y5 = Equal<{ name: string }, { name?: string }>; // false
type User1 = {
name?: string;
age: number;
address: string;
};
type User2 = {
name?: string;
} & {
age: number;
address: string;
};
type Y6 = Equal<User1, User2>; // true
type Y7 = Equal<true, boolean>; // false
type Y8 = Equal<1 | 2, 1>; // false
type Y9 = Equal<any, string>; // true,错误
type Y10 = Equal<{ name: string }, { readonly name: string }>; // true,错误
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type Equal<A, B> = [A, B] extends [B, A] ? true : false;
type Y1 = Equal<string, string>; // true
type Y2 = Equal<string, number>; // false
type Y3 = Equal<{ name: string }, { name: string }>; // true
type Y4 = Equal<{ name: string }, { age: number }>; // false
type Y5 = Equal<{ name: string }, { name?: string }>; // false
type User1 = {
name?: string;
age: number;
address: string;
};
type User2 = {
name?: string;
} & {
age: number;
address: string;
};
type Y6 = Equal<User1, User2>; // false
type Y7 = Equal<true, boolean>; // false
type Y8 = Equal<1 | 2, 1>; // false
type Y9 = Equal<any, string>; // false
type Y10 = Equal<{ name: string }, { readonly name: string }>; // false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# EqualAll
type Test1 = [1, 1, 1]
type Test2 = [1, 1, 2]
type Todo = EqualAll<Test1, 1> // should be same as true
type Todo2 = EqualAll<Test2, 1> // should be same as false
type Equal<A, B> = [A, B] extends [B, A] ? true : false;
type EqualAll<T extends unknown[], U> = Equal<T[number], U>;
2
3
4
5
6
7
8
9
10
# GetReadonlyKeys
interface Todo {
readonly title: string;
readonly description: string;
completed: boolean;
}
type Keys = GetReadonlyKeys<Todo>; // expected to be "title" | "description"
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
// 方法一
type GetReadonlyKeys<T> = keyof {
[K in keyof T as Equal<Pick<T, K>, Readonly<Pick<T, K>>> extends true ? K : never]: T[K]
};
// 方法二
type GetReadonlyKeys<T> = {
[P in keyof Required<T>]: Equal<{ [K in P]: T[K] }, { -readonly [R in P]: T[R] }> extends true ? never : P;
}[keyof T];
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Readonly
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
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
拓展:
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> = Omit<T, K> & Readonly<T>;
// 方法二
type MyReadonly2<T, K extends keyof T> = {
+readonly [P in K]: T[P]
} & {
[key in keyof T as key extends K ? never : key]: T[key]
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# DeepReadOnly
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 [P in keyof T]: keyof T[P] extends never ? T[P] : DeepReadonly<T[P]>;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# TupleToUnion
type Arr = ["1", "2", "3"];
type Test = TupleToUnion<Arr>; // expected to be '1' | '2' | '3'
// 方法一
type TupleToUnion<T extends any[]> = T[number];
// 方法二
type TupleToUnion<T extends any[]> = T extends Array<infer U> ? U : never;
2
3
4
5
6
7
8
9
# TupleToObject
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'}
// 方法一
type TupleToObject<T extends readonly any[]> = {
[P in T[number]]: P;
};
// 方法二
type TupleToObject<T extends readonly PropertyKey[]> = {
[P in T[number]]: P;
};
2
3
4
5
6
7
8
9
10
11
12
13
# Chainable
declare const config: Chainable;
const result = config
.option("foo", 123)
.option("name", "type-challenges")
.option("bar", { value: "Hello World" })
.get();
// expect the type of result to be: interface Result {foo: number, name: string, bar: {value: string}}
type Chainable<T = {}> = {
option<K extends PropertyKey, V>(
key: K extends keyof T ? never : K,
value: V
): Chainable<Omit<T, K> & { [P in K]: V }>;
get(): T;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Currying
const add = (a: number, b: number) => a + b;
const three = add(1, 2);
type CurryingFn<F extends Function> = F extends (
first: infer First,
...rest: infer Rest
) => infer Ret
? Rest["length"] extends 0
? F
: (first: First) => CurryingFn<(...args: Rest) => Ret>
: never;
function Currying<F extends Function>(fn: F): CurryingFn<F>;
function Currying(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args1) {
return curried.apply(this, args.concat(args1));
};
}
};
}
const curriedAdd = Currying(add);
const five = curriedAdd(2)(3);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# First
type arr1 = ["a", "b", "c"];
type arr2 = [3, 2, 1];
type head1 = First<arr1>; // expected to be 'a'
type head2 = First<arr2>; // expected to be 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 extends [infer F, ...any] ? F : never;
2
3
4
5
6
7
8
9
10
11
12
13
14
# Last
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 [...any, infer L] ? L : never;
// 方法二
type Last<T extends any[]> = T["length"] extends 0
? never
: [never, ...T][T["length"]];
2
3
4
5
6
7
8
9
10
11
12
13
# Pop
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]
// 方法一
type Pop<T extends any[]> = T extends [...(infer A), infer _] ? A : never;
// 方法二
type Pop<T extends any[]> = T extends [...(infer A), any] ? A : never;
2
3
4
5
6
7
8
9
10
11
# Length
type tesla = ["tesla", "model 3", "model X", "model Y"];
type spaceX = [
"FALCON 9",
"FALCON HEAVY",
"DRAGON",
"STARSHIP",
"HUMAN SPACEFLIGHT"
];
type teslaLength = Length<tesla>; // expected 4
type spaceXLength = Length<spaceX>; // expected 5
// 方法一
type Length<T extends any[]> = T["length"];
// 方法二
type Length<T extends any[]> = T extends { length: infer L } ? L : never;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# MyExclude
type Result = MyExclude<"a" | "b" | "c", "a">; // 'b' | 'c'
type MyExclude<T, U> = T extends U ? never : T;
2
3
# GetRequired
type I = GetRequired<{ foo: number, bar?: string }> // expected to be { foo: number }
type GetRequired<T> = {
[P in keyof T as Omit<T, P> extends T ? never : P]: T[P]
};
2
3
4
5
# PromiseAll
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 = PromiseAll([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] }>;
2
3
4
5
6
7
8
9
10
11
12
# RequiredKeys
type Result = RequiredKeys<{ foo: number; bar?: string }>; // expected to be “foo”
type RequiredKeys<T, K = keyof T> = K extends keyof T
? T extends Required<Pick<T, K>>
? K
: never
: never;
2
3
4
5
6
7
# MyCapitalize
type capitalized = MyCapitalize<'hello world'> // expected to be 'Hello world'
type MyCapitalize<S extends string> = S extends `${infer F}${infer R}`
? `${Uppercase<F>}${R}`
: S;
2
3
4
5
# Trim
type trimed = TrimLeft<' Hello World '> // expected to be 'Hello World '
type WhiteSpace = ' ' | '\t' | '\n';
type TrimLeft<S extends string> = S extends `${WhiteSpace}${infer R}`
? TrimLeft<R>
: S;
type trimmed = Trim<' Hello World '> // expected to be 'Hello World'
type Trim<S extends string> = S extends `${WhiteSpace}${infer T}` | `${infer T}${WhiteSpace}`
? Trim<T>
: S;
2
3
4
5
6
7
8
9
10
11
12
13
# Replace
type replaced = Replace<"types are fun!", "fun", "awesome">; // expected to be '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;
type replacedall = ReplaceAll<"t y p e s", " ", "">; // expected to be '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;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# OmitByType
type OmitBoolean = OmitByType<
{
name: string;
count: number;
isReadonly: boolean;
isEnable: boolean;
},
boolean
>; // { name: string; count: number }
type OmitByType<T, U> = {
[K in keyof T as T[K] extends U ? never : K]: T[K];
};
2
3
4
5
6
7
8
9
10
11
12
13
# 将数组强制转换成元组
function tuplify<T extends unknown[]>(...elements: T): T {
return elements;
}
2
3
具体应用场景如下:
function test() {
const data = ["test", 1];
return data;
}
const items = test();
const [item] = items;
2
3
4
5
6
7
此时会发现,item 的类型并不是预期的 string,而是 string | number,它身上也只有 string 和 number 共有的方法,string 独有的方法没了。要想解决这个问题,有以下方法:
// 方法一
function test() {
const a: string = "test";
const b: number = 1;
return [a, b] as const;
// 或者
// return <const>[a, b]; // 比较少见
}
const items = test();
const [item] = items;
2
3
4
5
6
7
8
9
10
11
// 方法二
function test() {
const a: string = "test";
const b: number = 1;
return tuplify(a, b);
}
const items = test();
const [item] = items;
2
3
4
5
6
7
8
9
另一个应用场景:
function tuplify<T extends any[]>(...elements: T) {
return elements;
}
function useArray() {
const numberValue = useRef(3).current;
const functionValue = useRef(() => {}).current;
return [numberValue, functionValue]; // type is (number | (() => void))[]
}
function useTuple() {
const numberValue = useRef(3).current;
const functionValue = useRef(() => {}).current;
return tuplify(numberValue, functionValue); // type is [number, () => void]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用泛型约束提前确保属性存在
function getProperty<T extends object, K extends keyof T>(o: T, name: K): T[K] {
return o[name];
}
2
3
具体应用场景如下:
const data = {
hello: "world"
};
function getProperty(o: object, name: string) {
return o[name];
}
const result = getProperty(data, "hello");
2
3
4
5
6
7
8
9
此时 getProperty 函数里会报错,元素隐式具有 "any" 类型,因为类型为 "string" 的表达式不能用于索引类型 "{}"。在类型 "{}" 上找不到具有类型为 "string" 的参数的索引签名。
因此可以使用上面那种泛型约束的方式来实现获取属性的函数。
# NonNullable
NonNullable (opens new window) 是 ts 的内置工具类型,用于从类型中排除 null 和 undefined。
type NonNullableType = string | number;
function showType(args: NonNullable<NonNullableType>) {
console.log("args: ", args);
}
showType(null); // Error
showType(undefined); // Error
showType(1); // OK
2
3
4
5
6
7
8
# 确保获取到的 dom 元素不为 null
当我们获取 dom 元素进行一些操作或者属性获取时,由于获取到的 dom 元素有可能是 null,因此会报错。
此外,要想在 ts 使用 dom,还需要在 tsconfig.json 中配置相应的 dom 库:"lib": ["DOM"]
。
针对以上问题,有下面这些解决方法:
// 方法一
// 自己手动做不为 null 的判断
const textEl = document.querySelector<HTMLInputElement>("input");
if (textEl !== null) {
console.log(textEl.value);
}
2
3
4
5
6
7
// 方法二
// 强制限制获取到的 dom 的类型不为 null
const textEl = <HTMLInputElement>document.querySelector("input");
// 或者
// const textEl = document.querySelector('input') as HTMLInputElement;
console.log(textEl.value);
2
3
4
5
6
// 方法三
let textEl = document.querySelector<HTMLInputElement>("input");
// textEl = !null; 慎用
console.log(textEl!.value);
2
3
4
# 合并两个对象的属性
type Merge<O1 extends object, O2 extends object> = Compute<
O1 & Omit<O2, keyof O1>
>;
type O1 = {
name: string;
id: number;
};
type O2 = {
id: number;
from: string;
};
type R2 = Merge<O1, O2>; // { name: string; id: number; from: string; }
2
3
4
5
6
7
8
9
10
11
12
13
# 取出 T,U 的共同属性
type Intersection<T extends object, U extends object> = Pick<
T,
Extract<keyof T, keyof U> & Extract<keyof U, keyof T>
>;
type Props = {
name: string;
age: number;
visible: boolean;
};
type DefaultProps = {
age: number;
};
type DuplicatedProps = Intersection<Props, DefaultProps>; // { age: number; }
2
3
4
5
6
7
8
9
10
11
12
13
14
# ⽤ U 的属性覆盖 T 的相同属性
type Overwrite<
T extends object,
U extends object,
I = Diff<T, U> & Intersection<U, T>
> = Pick<I, keyof I>;
type Props1 = {
name: string;
age: number;
visible: boolean;
};
type NewProps = {
age: string;
other: string;
};
type ReplacedProps = Overwrite<Props, NewProps>; // { name: string; age: string; visible: boolean; }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 将 T 中所有属性的 readonly 移除
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
type mutable = {
readonly a: number;
b: string;
};
type immutable = Mutable<mutable>; // { a: number; b: string; }
2
3
4
5
6
7
8
9
# T 类型可能是 User 类型的子类型,它可能会包含更多的属性
type User = {
id: number;
kind: string;
};
function makeCustomer<T extends User>(u: T): T {
// Error(TS 编译器版本:v4.4.2)
// Type '{ id: number; kind: string; }' is not assignable to type 'T'.
// '{ id: number; kind: string; }' is assignable to the constraint of type 'T',
// but 'T' could be instantiated with a different subtype of constraint 'User'.
return {
id: u.id,
kind: "customer"
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
可以利用 ...
展开运算符来解决这个问题。
type User = {
id: number;
kind: string;
};
// T assignable User
function makeCustomer<T extends User>(u: T): T {
return {
...u,
id: u.id,
kind: "customer"
};
}
2
3
4
5
6
7
8
9
10
11
12
13
# 参数类型保持一致
希望参数 a 和 b 的类型都是一致的,即 a 和 b 同时为 number 或 string 类型。当它们的类型不一致的值,TS 类型检查器能自动提示对应的错误信息。
function f(a: string | number, b: string | number) {
if (typeof a === "string") {
return a + ":" + b; // no error but b can be number!
} else {
return a + b; // error as b can be number | string
}
}
f(2, 3); // Ok
f(1, "a"); // Error
f("a", 2); // Error
f("a", "b"); // Ok
2
3
4
5
6
7
8
9
10
11
12
解决方法有:
// 函数重载
function f(a: string, b: string): string;
function f(a: number, b: number): number;
function f(a: string | number, b: string | number): string | number {
if (typeof a === "string") {
return a + ":" + b;
} else {
return (a as number) + (b as number);
}
}
f(2, 3); // Ok
f(1, "a"); // Error
f("a", 2); // Error
f("a", "b"); // Ok
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 把参数组合成一种类型
const isStrArr = (a: string[] | number[]): a is string[] =>
typeof a[0] === "string";
function f(...args: string[] | number[]) {
if (isStrArr(args)) {
return args[0] + ":" + args[1];
} else {
return args[0] + args[1];
}
}
f(2, 3); // Ok
f(1, "a"); // Error
f("a", 2); // Error
f("a", "b"); // Ok
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 把给定的 keys 对应的属性变成可选的
type Foo = {
a: number;
b?: string;
c: boolean;
};
// Simplify 用来对交叉类型进行扁平化处理
type Simplify<T> = {
[P in keyof T]: T[P];
};
type SetOptional<T, K extends keyof T> = Simplify<
Partial<Pick<T, K>> & Pick<T, Exclude<keyof T, K>>
>;
// 测试用例
type SomeOptional = SetOptional<Foo, "a" | "b">;
// type SomeOptional = {
// a?: number; // 该属性已变成可选的
// b?: string; // 保持不变
// c: boolean;
// }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 把给定的 keys 对应的属性变成必填的
type Foo = {
a?: number;
b: string;
c?: boolean;
};
// Simplify 用来对交叉类型进行扁平化处理
type Simplify<T> = {
[P in keyof T]: T[P];
};
type SetRequired<T, K extends keyof T> = Simplify<
Pick<T, Exclude<keyof T, K>> & Required<Pick<T, K>>
>;
// 测试用例
type SomeRequired = SetRequired<Foo, "b" | "c">;
// type SomeRequired = {
// a?: number;
// b: string; // 保持不变
// c: boolean; // 该属性已变成必填
// }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 根据指定的条件来生成新的类型
先定义一个 ConditionalKeys 工具类型,来获取所有满足 Condition 条件的 Keys,然后在利用 TS 内置的 Pick 工具类型来最终实现 ConditionalPick 的功能。
type ConditionalKeys<T, Condition> = {
[P in keyof T]: T[P] extends Condition ? P : never;
}[keyof T];
type ConditionalPick<T, Condition> = Pick<T, ConditionalKeys<T, Condition>>;
// 测试用例:
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 为已有的函数类型增加指定类型的参数
定义一个工具类型 AppendArgument,为已有的函数类型增加指定类型的参数,新增的参数名是 x,将作为新函数类型的第一个参数。
// 使用 Parameters 和 ReturnType 工具类型
type AppendArgument<F extends (...args: any) => any, A> = (
x: A,
...args: Parameters<F>
) => ReturnType<F>;
type Fn = (a: number, b: string) => number;
type FinalF = AppendArgument<Fn, boolean>;
// (x: boolean, a: number, b: string) => number
2
3
4
5
6
7
8
9
// 使用 infer
type AppendArgument<F, T> = F extends (...args: infer Args) => infer Return
? (x: T, ...args: Args) => Return
: never;
type Fn = (a: number, b: string) => number;
type FinalFn = AppendArgument<Fn, boolean>;
// (x: boolean, a: number, b: string) => number
2
3
4
5
6
7
8
# 支持将数组类型扁平化
定义一个 NativeFlat 工具类型,支持把数组类型拍平(扁平化)。
type NaiveFlat<T extends any[]> = {
[P in keyof T]: T[P] extends any[] ? T[P][number] : T[P];
}[number];
type NaiveResult = NaiveFlat<[["a"], ["b", "c"], ["d"]]>;
// NaiveResult 的结果: "a" | "b" | "c" | "d"
2
3
4
5
6
# 支持将多维数组扁平化
type Deep = [["a"], ["b", "c"], [["d"]], [[[["e"]]]]];
type DeepFlat<T extends any[]> = {
[K in keyof T]: T[K] extends any[] ? DeepFlat<T[K]> : T[K];
}[number];
type DeepTestResult = DeepFlat<Deep>;
// DeepTestResult: "a" | "b" | "c" | "d" | "e"
2
3
4
5
6
7
8
# 只允许空对象赋值
使用类型别名定义一个 EmptyObject 类型,使得该类型只允许空对象赋值。
type EmptyObject = {
// type PropertyKey = string | number | symbol
[K in PropertyKey]: never;
};
// 测试用例
const shouldPass: EmptyObject = {}; // 可以正常赋值
const shouldFail: EmptyObject = {
// 将出现编译错误
prop: "TS"
};
2
3
4
5
6
7
8
9
10
11
# 只允许严格某些类型的值
type SomeType = {
prop: string;
};
type Exclusive<T1, T2 extends T1> = {
[K in keyof T2]: K extends keyof T1 ? T2[K] : never;
};
// 更改以下函数的类型定义,让它的参数只允许严格SomeType类型的值
function takeSomeTypeOnly<T extends SomeType>(x: Exclusive<SomeType, T>) {
return x;
}
// 测试用例:
const x = { prop: "a" };
takeSomeTypeOnly(x); // 可以正常调用
const y = { prop: "a", addditionalProp: "x" };
takeSomeTypeOnly(y); // 将出现编译错误
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 确保数组非空
// 方法一
type NonEmptyArray<T> = [T, ...T[]];
const a: NonEmptyArray<string> = []; // 将出现编译错误
const b: NonEmptyArray<string> = ["Hello TS"]; // 非空数据,正常使用
2
3
4
5
// 方法二
type NonEmptyArray<T> = T[] & { 0: T };
const a: NonEmptyArray<string> = []; // 将出现编译错误
const b: NonEmptyArray<string> = ["Hello TS"]; // 非空数据,正常使用
2
3
4
5
# 根据指定分隔符对字符串数组类型进行拼接
定义一个 JoinStrArray 工具类型,用于根据指定的 Separator 分隔符,对字符串数组类型进行拼接。
// 利用模板字面量类型和递归条件类型来实现
type JoinStrArray<Arr extends string[], Separator extends string, Result extends string = ""> =
Arr extends [infer El,...infer Rest] ?
Rest extends string[] ?
El extends string ?
Result extends "" ?
JoinStrArray<Rest, Separator,`${El}`> :
JoinStrArray<Rest, Separator,`${Result}${Separator}${El}`> :
`${Result}` :
`${Result}` :
`${Result}`
type Names = ["Sem", "Lolo", "Kaquko"]
type NamesComma = JoinStrArray<Names, ","> // "Sem,Lolo,Kaquko"
type NamesSpace = JoinStrArray<Names, " "> // "Sem Lolo Kaquko"
type NamesStars = JoinStrArray<Names, "⭐️"> // "Sem⭐️Lolo⭐️Kaquko"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 对字符串字面量类型进行去空格处理
实现一个 Trim 工具类型,用于对字符串字面量类型进行去空格处理。
// 可以先定义 TrimLeft 和 TrimRight 工具类型,再组合成 Trim 工具类型
type TrimLeft<V extends string> = V extends ` ${infer R}` ? TrimLeft<R> : V;
type TrimRight<V extends string> = V extends `${infer R} ` ? TrimRight<R> : V;
type Trim<V extends string> = TrimLeft<TrimRight<V>>;
// 测试用例
type trim = Trim<' semlinker '>
//=> 'semlinker'
2
3
4
5
6
7
8
9