# 内置工具类型

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;
1
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]
};
1
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];
};
1
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,错误
1
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,错误
1
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
1
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>;
1
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];
1
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];
};
1
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]
};
1
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]>;
};
1
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;
1
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;
};
1
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;
};
1
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);
1
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;
1
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"]];
1
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;
1
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;
1
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;
1
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]
};
1
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] }>;
1
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;
1
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;
1
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;
1
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;
1
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];
};
1
2
3
4
5
6
7
8
9
10
11
12
13

# 将数组强制转换成元组

function tuplify<T extends unknown[]>(...elements: T): T {
  return elements;
}
1
2
3

具体应用场景如下:

function test() {
  const data = ["test", 1];
  return data;
}

const items = test();
const [item] = items;
1
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;
1
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;
1
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]
}
1
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];
}
1
2
3

具体应用场景如下:

const data = {
  hello: "world"
};

function getProperty(o: object, name: string) {
  return o[name];
}

const result = getProperty(data, "hello");
1
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
1
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);
}
1
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);
1
2
3
4
5
6
// 方法三
let textEl = document.querySelector<HTMLInputElement>("input");
// textEl = !null; 慎用
console.log(textEl!.value);
1
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; }
1
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; }
1
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; }
1
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; }
1
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"
  };
}
1
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"
  };
}
1
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
1
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
1
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
1
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;
// }
1
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; // 该属性已变成必填
// }
1
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}
1
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
1
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
1
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"
1
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"
1
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"
};
1
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); // 将出现编译错误
1
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"]; // 非空数据,正常使用
1
2
3
4
5
// 方法二
type NonEmptyArray<T> = T[] & { 0: T };

const a: NonEmptyArray<string> = []; // 将出现编译错误
const b: NonEmptyArray<string> = ["Hello TS"]; // 非空数据,正常使用
1
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"
1
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'
1
2
3
4
5
6
7
8
9
上次更新时间: 2024年01月23日 15:41:08