梳理常用的TypeScript内置工具类型

在 TypeScript 中,对一个对象进行类型标注,如下:

js 复制代码
interface IUser {
  name: string
  age?: number
}

发现,age不是可选了,而是必选的,那怎么办呢?难道你再写一个像如下的接口吗?

js 复制代码
interface IUser1 {
  name: string
  age: number
}

这肯定傻透了。TS 其实早就想到了,它提供了工具类型,这样我们就可以愉快的进行修改。

下图是 TS 官网 提供的工具类型,下面我将在开发中常用的工具类型一一介绍。

对象类型

在开发中,我们打交道最多的就是对象类型,因此,对象类型的工具类型占据大多数。

Partial

它接收一个对象类型,并将这个对象类型的所有属性都标记为可选,这样我们就不需要一个个将它们标记为可选属性了。

js 复制代码
type User = {
  name: string;
  age: number;
  email: string;
};

type PartialUser = Partial<User>;

const user: User = {
  name: 'John Doe',
  age: 30,
  email: 'john.doe@example.com'
};

// 可以不实现全部的属性了!
const partialUser: PartialUser = {
  name: 'John Doe',
  age: 30
};

从 Partial 的使用方式我们可以看到,工具类型使用起来就像一个函数------你给它入参,它还你出参,而出入参都是类型!而这些函数预留的参数,就是类型世界中的参数:泛型

那如果反过来,一开始我们有的就是一个内部属性均为可选的对象类型,现在需要变成必选呢?

这就要说到另一个类型编程的规律了:大部分工具类型都是成对出现的,有将属性标记为可选的 Partial,就会有将属性标记为必选的 Required,它的使用方式和 Partial 完全一致。

Required

js 复制代码
type User = {
  name?: string;
  age?: number;
  email?: string;
};

type RequiredUser = Required<User>;

const user: User = {
  name: 'John Doe'
};

// 现在你必须全部实现这些属性了
const requiredUser: RequiredUser = {
  name: 'John Doe',
  age: 30,
  email: 'john.doe@example.com'
};

Readonly

用于将属性标记为只读。

js 复制代码
type User = {
  name: string;
  age: number;
  email: string;
};

type ReadonlyUser = Readonly<User>;

const user: User = {
  name: 'John Doe',
  age: 30,
  email: 'john.doe@example.com'
};

const readonlyUser: ReadonlyUser = {
  name: 'John Doe',
  age: 30,
  email: 'john.doe@example.com'
};

// 修改 user 对象的属性
user.name = 'Jane Doe';
user.age = 25;
user.email = 'jane.doe@example.com';

// 修改 readonlyUser 对象的属性
// readonlyUser.name = 'Jane Doe';  // 报错
// readonlyUser.age = 25;  // 报错
// readonlyUser.email = 'jane.doe@example.com';  // 报错

需要注意的是,TypeScript 内置的工具类型中,并不包括与 Readonly 结对出现的版本,你可以认为,只读通常是一个不可逆的行为,如果能够随意将只读修饰移除,就可能破坏了只读带来的安全性。

Record

我们知道,当接口描述不确定属性时,有一个可索引接口来表示:

TypeScript 中基于索引签名类型提供了一个简化版本 Record,它能够用更简洁的语法实现同样的效果:

js 复制代码
type UserProps = 'name' | 'job' | 'email';

// 等价于你一个个实现这些属性了
type User = Record<UserProps, string>;

const user: User = {
  name: 'John Doe',
  job: 'fe-developer',
  email: 'john.doe@example.com'
};

你可以使用 Record 类型来声明属性名还未确定的接口类型,如:

js 复制代码
type User = Record<string, string>;

const user: User = {
  name: 'John Doe',
  job: 'fe-developer',
  email: 'john.doe@example.com',
  bio: 'Make more interesting things!',
  type: 'vip',
  // ...
};

Pick 与 Omit

除了对象类型的声明与属性修饰,内置工具类型中还包括用于对象类型裁剪的 Pick 与 Omit,效果类似于 Lodash 中的 Pick 与 Omit 方法。

Pick 类型接收一个对象类型,以及一个字面量类型组成的联合类型,这个联合类型只能是由对象类型的属性名组成的。它会对这个对象类型进行裁剪,只保留你传入的属性名组成的部分:

js 复制代码
type User = {
  name: string;
  age: number;
  email: string;
  phone: string;
};

// 只提取其中的 name 与 age 信息
type UserBasicInfo = Pick<User, 'name' | 'age'>;

const user: User = {
  name: 'John Doe',
  age: 30,
  email: 'john.doe@example.com',
  phone: '1234567890'
};

const userBasicInfo: UserBasicInfo = {
  name: 'John Doe',
  age: 30
};

Omit 类型就是 Pick 类型的另一面,它的入参和 Pick 类型一致,但效果却是相反的------它会移除传入的属性名的部分,只保留剩下的部分作为新的对象类型:

js 复制代码
type User = {
  name: string;
  age: number;
  email: string;
  phone: string;
};

// 只移除 phone 属性
type UserWithoutPhone = Omit<User, 'phone'>;

const user: User = {
  name: 'John Doe',
  age: 30,
  email: 'john.doe@example.com',
  phone: '1234567890'
};

const userWithoutPhone: UserWithoutPhone = {
  name: 'John Doe',
  age: 30,
  email: 'john.doe@example.com'
};

Pick 与 Omit 类型是类型编程中相当重要的一个部分,举例来说,我们可以先声明一个代表全局所有状态的大型接口类型:

js 复制代码
type User = {
  name: string;
  age: number;
  email: string;
  phone: string;
  address: string;
  gender: string;
  occupation: string;
  education: string;
  hobby: string;
  bio: string;
};


type UserBasicInfo = Pick<User, 'name' | 'age' | 'email'>;

const userBasicInfo: UserBasicInfo = {
  name: 'John Doe',
  age: 30,
  email: 'john.doe@example.com'
};

type UserDetailedInfo = Omit<User, 'name' | 'age' | 'email'>;

const userDetailedInfo: UserDetailedInfo = {
  phone: '1234567890',
  address: '123 Main St',
  gender: 'male',
  occupation: 'developer',
  education: 'Bachelor',
  hobby: 'reading',
  bio: 'A passionate developer'
};

集合类型

集合类型主要有两个工具类型: ExcludeExtract,这两个名字可能不太好理解,但如果我说 差集交集 你就懂了。

Exclude

它能够从一个类型中移除另一个类型中也存在的部分:

js 复制代码
type UserProps = 'name' | 'age' | 'email' | 'phone' | 'address';
type RequiredUserProps = 'name' | 'email';

// OptionalUserProps = UserProps - RequiredUserProps
type OptionalUserProps = Exclude<UserProps, RequiredUserProps>;

const optionalUserProps: OptionalUserProps = 'age' | 'phone' | 'address';

Extract

用于提取另一个类型中也存在的部分,即交集。

js 复制代码
type UserProps = 'name' | 'age' | 'email' | 'phone' | 'address';
type RequiredUserProps = 'name' | 'email';

type RequiredUserPropsOnly = Extract<UserProps, RequiredUserProps>;

const requiredUserPropsOnly: RequiredUserPropsOnly = 'name' | 'email';

函数类型

不妨先想想,对于函数类型,工具类型能起到什么作用?

函数类型=参数类型+返回值类型,这个定律适用于所有的函数类型定义。而我们一般又不会去修改参数与返回值位置的类型,那就只剩下读取了。

内置工具类型中提供了 ParametersReturnType 这两个类型来提取函数的参数类型与返回值类型。

js 复制代码
type Add = (x: number, y: number) => number;

type AddParams = Parameters<Add>; // [number, number] 类型
type AddResult = ReturnType<Add>; // number 类型

const addParams: AddParams = [1, 2];
const addResult: AddResult = 3;

那么如果,我们只有一个函数,而并没有这个函数类型呢?

此时可以使用 TypeScript 提供的类型查询操作符,即 typeof(记得和 JavaScript 的 typeof 区分一下),来获得一个函数的结构化类型,再配合工具类型即可即可:

js 复制代码
const addHandler = (x: number, y: number) => x + y;

type Add = typeof addHandler; // (x: number, y: number) => number;

type AddParams = Parameters<Add>; // [number, number] 类型
type AddResult = ReturnType<Add>; // number 类型

const addParams: AddParams = [1, 2];
const addResult: AddResult = 3;

你可能会想到,对于异步函数类型,提取出的返回值类型是一个 Promise<string> 这样的类型,如果我想提取 Promise 内部的 string 类型呢?贴心的 TypeScript 为你准备了 Awaited 类型用于解决这样的问题:

js 复制代码
// 定义一个函数,该函数返回一个 Promise 对象
async function getPromise() {
  return new Promise<string>((resolve) => {
    setTimeout(() => {
      resolve("Hello, World!");
    }, 1000);
  });
}

type Result = Awaited<ReturnType<typeof getPromise>>; // string 类型

以上这些虽然不是全部的 TypeScript 内置工具类型,但一定是最常用的部分。熟练使用这些内置工具类型,在开发中一定会如鱼得水。

相关推荐
bobostudio19952 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
Tiffany_Ho9 小时前
【TypeScript】知识点梳理(三)
前端·typescript
看到请催我学习14 小时前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
天涯学馆20 小时前
Deno与Secure TypeScript:安全的后端开发
前端·typescript·deno
applebomb1 天前
【2024】uniapp 接入声网音频RTC【H5+Android】Unibest模板下Vue3+Typescript
typescript·uniapp·rtc·声网·unibest·agora
读心悦1 天前
TS 中类型的继承
typescript
读心悦1 天前
在 TS 的 class 中,如何防止外部实例化
typescript
Small-K2 天前
前端框架中@路径别名原理和配置
前端·webpack·typescript·前端框架·vite
宏辉2 天前
【TypeScript】异步编程
前端·javascript·typescript
LJ小番茄3 天前
TS(type,属性修饰符,抽象类,interface)一次性全部总结
前端·javascript·vue.js·typescript