TS内置工具类介绍

111 阅读6分钟

在ts中为了方便我们快速且灵活的对数据进行类型约束,TS内部给我们提供了许多内置工具类:

  • Partial 将对象类型中的所有属性都设置为可选类型

    type User = {
      name: string;
      age: number;
      sex: string;
    };
    
    type PartialUser = Partial<User>
    
    const user: User = {
      name: 'Tom',
      age: 30,
      sex: '女';
    };
    
    const partialUser: PartialUser = {
      name: 'Tome',
      age: 30
    };
    
    //相当于将User类型转换成
    type User = {
      name?: string;
      age?: number;
      sex?: string;
    };
    

    从上述例子中我们可以看到所谓的工具类就是你给他传入一个类型他会返回给你一个新的类型,

    假如我么人呢将传入进去的User类型变成泛型T是不是就可以将不同的对象类型都变成属性值未可选的对象类型

  • Required 将对象类型中的属性全部变为必填属性

    type User = {
      name?: string;
      age?: number;
      sex?: string;
    };
    
    type RequiredUser = Required<User>
    
    //此时就会报错 因为 此时User中的属性必须全部实现
    const RequiredUser: RequiredUser = {
      name: 'Tome',
      age: 30
    };
    
    ✅
    const RequiredUser: RequiredUser = {
      name: 'Tome',
      age: 30,
      sex: '女';
    };
    

    除了可选以外,还有一个 readonly 修饰,用于将属性标记为只读,类似于 Partial,TypeScript 中也内置了一个用于将对象类型所有属性标记为只读的工具类型 Readonly:

  • Readonly 将对象类型中的属性变为只读属性

    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';  // 报错
    
    

    其实我们也可以去手动实现这些工具类这样会让我们更容易理解ts

    例如: Readonly工具类的实现

    type MyReadonly<T> = {readonly [P in keyof T]: T[P]}
    //这里我们就通过映射来实现了readonly工具类
    
  • Record 声明一个内部属性键与键值类型一致的对象类型

    type UserProps = 'name' | 'job' | 'email';
    
    // 等价于你一个个实现这些属性了
    type User = Record<UserProps, string>;
    
    const user: User = {
      name: 'John Doe',
      job: 'fe-developer',
      email: 'john.doe@example.com'
    };
    
    //立马就实现了基于联合类型中的内容生成对象类型
    
    //实现思路将联合类型中的内容变成对象类型的属性,每个属性的的值的类型都为string
    
    其实我们也可以换个思路 看到联合类型 'name' | 'job' | 'email';我们有想到什么?
     元祖中有不同类型的时候是不是也这样提示
    //假设场景 你得到的数据是一个数组,这个数组中有["name", "job", "age"] 那你只需要 数组[number]就可以得到数组值的联合类型了而不需要自己手动声明了
    
    type list = ["name", "job", "age"];
    type MyList = list[number]; // "name" | "job" | "age"
    
    type User = Record<MyList, string>;
    
    const user: User = {
      name: 'John Doe',
      job: 'fe-developer',
      age: '20'
    };
    

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

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

    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 类型一致,但效果却是相反的

  • Omit 会移除传入的属性名的部分,只保留剩下的部分作为新的对象类型:

    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 类型是类型编程中相当重要的一个部分,举例来说,我们可以先声明一个代表全局所有状态的大型接口类型:

    type User = {
      name: string;
      age: number;
      email: string;
      phone: string;
      address: string;
      gender: string;
      occupation: string;
      education: string;
      hobby: string;
      bio: string;
    };
    

    然后在我们的子组件中,可能只用到了其中一部分的类型,此时就可以使用 Pick 类型将我们需要的部分择出来:

    type UserBasicInfo = Pick<User, 'name' | 'age' | 'email'>;
    
    const userBasicInfo: UserBasicInfo = {
      name: 'John Doe',
      age: 30,
      email: 'john.doe@example.com'
    };
    

    反之,如果我们用到了大部分类型,只有数个类型需要移除,就可以使用 Omit 类型来减少一些代码量:

    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'
    };
    
  • Exclude 首先是代表差集的 Exclude,它能够从一个类型中移除另一个类型中也存在的部分:

    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 而 Extract 则用于提取另一个类型中也存在的部分,即交集:

    type UserProps = 'name' | 'age' | 'email' | 'phone' | 'address';
    type RequiredUserProps = 'name' | 'email';
    
    type RequiredUserPropsOnly = Extract<UserProps, RequiredUserProps>;
    
    const requiredUserPropsOnly: RequiredUserPropsOnly = 'name' | 'email';
    

函数(类型)

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

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 区分一下),来获得一个函数的结构化类型,再配合工具类型即可即可:

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;

//type MyReturnType<T extends Function> =T extends (...args: any[]) => infer R ? R : never;
//type MyParameters<T extends Function> = T extends (...args: infer R) => [...R] : never

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

const promise = new Promise<string>((resolve) => {
  setTimeout(() => {
    resolve("Hello, World!");
  }, 1000);
});

type PromiseInput = Promise<string>;
type AwaitedPromiseInput = Awaited<PromiseInput>; // string

你可以直接嵌套在 ReturnType 内部使用:

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

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

手动实现Awaited工具类

type MyAwaited<T extends PromiseLike<any>> = T extends  PromiseLike<infer R>? R extends PromiseLike<infer R>? MyAwaited<R>:R:never

//可能会有小伙伴问为什么不是Promise<infer R>因为需要考虑到 { then: (onfulfilled: (arg: number) => any) => any }这种类型
//所以选择范围更广的PromiseLike

TS还有很多的工具类,路漫漫任重道远 珍惜生命 健康生活 加油!!!