TypeScript进阶实战

TypeScript 高级数据类型

Dictionary 和 NumericDictionary

Dictionary:一个对象,键是 string 类型。

NumericDictionary:一个对象,键是 number 类型。

注意:要手动实现下,通过「索引签名」+「范型」。

定义:

1
2
3
4
5
6
7
interface Dictionary<T = any> {
[index: string]: T;
}

interface NumericDictionary<T = any> {
[index: number]: T;
}

使用起来:

1
2
3
4
const data2: Dictionary<number> = {
a: 3,
b: 4,
};

Record

ts 原生支持,看下它的定义:

1
type Record<K extends string | number | symbol, T> = { [P in K]: T };

简单来说,对于类型 Record<KEY_TYPE, VALUE_TYPE> 声明的对象,键的类型就是 KEY_TYPE,值的类型是 VALUE_TYPE。

它和直接用 interface 有啥区别? 一般 Record 可以用来做字段扩展、将值的类型转化为指定的 VALUE_TYPE、将键的类型转化为指定的 KEY_TYPE。

例子:

1
2
3
4
5
6
7
8
9
10
11
interface Person {
name: string
age: number
}

type PersonType = Record<'location' | keyof Person, number>
// type PersonType = {
// location: number;
// name: number;
// age: number;
}

Pick

ts 原生支持:

1
type Pick<T, K extends keyof T> = { [P in K]: T[P] };

作用:将某个类型指定键挑出来,组成新的类型。

例如:

1
2
3
4
5
6
7
8
9
10
11
interface Student {
name: string;
age: number;
score: string;
}

type Person = Pick<Student, "age" | "name">;
// type Person = {
// age: number;
// name: string;
// }

Exclude

ts 原生支持:

1
2
// 提取T包含在U中的类型
type Extract<T, U> = T extends U ? T : never;

作用:将联合类型中的指定类型去掉。

例如:

1
2
3
type NUMBERS = 0 | 1 | 2 | "a" | "b";

type CORRECT_NUMBERS = Exclude<NUMBERS, "a" | "b">; // CORRECT_NUMBERS 类型是 0 | 1 | 2

Extract

作用:将公有属性提取出来。

例如:

1
2
3
type NUMBERS = 0 | 1 | 2 | "a" | "b";

type PARTIAL_NUMBERS = Extract<NUMBERS, 0 | 1>; // PARTIAL_NUMBERS 类型是 0 | 1

函数相关-Parameters

作用:获取函数参数类型。

例如:

1
2
3
function getPerson(name: string, value?: number): any {}

type F = Parameters<typeof getPerson>; // F 类型是 [name: string, value?: number]

函数相关-ReturnType

作用:获取函数返回类型。

例如:

1
2
3
function getPerson(name: string, value?: number): any {}

type F = ReturnType<typeof getPerson>; // F 类型是 any

TypeScript 高级操作符

const 断言

特点:

  • 字面量(数组、接口)类型变为 readonly
  • 字面量类型不能被扩展

举例:

不使用 const 断言:

1
2
3
4
5
const CONFIG_KEYS = ["name", "school", "country"];

type KEY_TYPES = typeof CONFIG_KEYS; // KEY_TYPES 是 string[]

type KEY_TYPE = typeof CONFIG_KEYS[number]; // KEY_TYPE 是 string

使用 const 断言,ts 推断更精确:

1
2
3
4
5
const CONST_CONFIG_KEYS = ["name", "school", "country"] as const;

type COPNST_KEY_TYPES = typeof CONST_CONFIG_KEYS; // COPNST_KEY_TYPES 是 readonly ["name", "school", "country"]

type CONST_KEY_TYPE = typeof CONST_CONFIG_KEYS[number]; // CONST_KEY_TYPE 是 "name" | "school" | "country"

typoef 和 type

typeof 在 js 中,可以获得对象类型.

在 ts 中,能够配合 type 关键词,将对象类型保存在类型字面量中。

1
2
3
4
const CONFIG_KEYS = ["name", "school", "country"];

console.log(typeof CONFIG_KEYS); // 输出;object
type KEY_TYPES = typeof CONFIG_KEYS; // KEY_TYPES 是 string[]

keyof

keyof 用来获取某种类型的所有键,注意不是某个变量。

例子:假设声明了一个 json 对象,然后想获取它的所有键的类型应该怎么办?

1
2
3
4
5
6
7
8
9
// 利用 const 断言,告诉解释器不能被扩展
const VARS = {
NODE_ENV: "production",
AUTHOR: "xintan",
} as const;
// 再获取它的类型
type VARS_TYPE = typeof VARS;
// 最后获取类型上的key的类型
type VARS_KEYS_TYPE = keyof VARS_TYPE; // "NODE_ENV" | "AUTHOR"

再来一个用例,比如断言Reflect.ownKeys()的返回结果的类型:

1
2
3
4
5
6
const obj = {
name: "12yuanxin3",
age: 22,
} as const;

const objKeys = <(keyof typeof obj)[]>Reflect.ownKeys(obj); // objKeys 类型是: ("name" | "age")[]

extends

基础的用法:可以用于 class 继承、interface 继承。除此之外,还用于“类型约束”。

用法 1: T extends U ? X : Y

用法:T extends U ? X : Y。这个比较难理解,而且很常见。其实就是:如果 T 包含的类型是 U 包含的类型的 ‘子集’,那么取结果 X,否则取结果 Y

举例:

1
2
type Diff<T, U> = T extends U ? never : T; // 找出T和U的差集
type Filter<T, U> = T extends U ? T : never; // 找出T和U的交集

用法 2: 范型约束

用法:<T extends Lengthwise>

举例:

1
2
3
4
5
6
7
8
interface Lengthwise {
length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}

如果写成 function loggingIdentity<T>(arg: T): T,就会报错:T 上不存在 length 属性。正如所见,约束范型,传入的 arg 必须有 length 属性。

infer

作用:针对 ts 推断的类型,声明一个字面量,在extends语句中使用。

举例:

1
2
3
4
5
6
7
8
9
10
// 获得函数的返回类型
// 首先,范型约束了 T 是传入的函数的类型
// 然后,利用 extends 扩展表达式 + infer,将函数返回类型设置为R
// 最后,因为对T进行了约束,所以表达式一定是 true,也就是一定返回 R
// 我的理解:第2和3步骤就是为了拿到函数返回类型
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;

例子中的infer R就是传入参数(某个函数)的返回类型,在这个表达式中,函数的返回类型被R变量代替。

:::warning infer 使用的位置
infer 声明的这个变量只能在 true 分支中使用
:::

infer的更多内容可以参考 https://jkchao.github.io/typescript-book-chinese/tips/infer.html#一些用例

  • ReturnType:获取函数返回类型(内置)
  • ConstructorParameters:获取构造函数类型(内置)
  • InstanceType :获取 class 类型(内置)
  • ElementOf :获取数组元素类型

反解 Promise

理解了 ReturnType,我写出了一个反解 Promise的 typescript 扩展类型:

1
2
3
4
5
type UnPromiseType<T extends (...args: any) => any> = T extends (
...args: any
) => Promise<infer U>
? U
: never;

使用效果:

1
2
3
4
5
async function main(): Promise<string> {
return "abc";
}

type returnTypeWithoutPromise = UnPromiseType<typeof main>; // returnTypeWithoutPromise 是 string

参考链接