深入浅出TypeScript | 青训营

64 阅读3分钟

学习参考:

高级类型

联合类型 |

  1. 定义

    多个基本类型组成联合类型时,类型可以是若干类型中之一:

    type T = boolean | string | number | { id: number };
    

    多个自定义类型组成联合类型时,类型为这些自定义类型中属性成员的交集

    type A = {
     versionstring;
     anumber;
    }
    type B = {
     versionstring;
     bstring;
    }
    type T = A | B; // T = { version: string }
    
  2. 常见作用

    函数接收的参数类型可以约束在几种类型当中,这样就解决了函数传入参数类型混乱,或者需要重复定义函数应对不同参数类型的麻烦:

    function printValue(value: number | string): void {
      if (typeof value === 'number') {
        console.log(`The number is: ${value}`);
      } else if (typeof value === 'string') {
        console.log(`The string is: ${value}`);
      } else {
        console.log('Invalid input!');
      }
    }
    
    printValue(42);         // The number is: 42
    printValue('Hello');    // The string is: Hello
    printValue(true);       // Invalid input!
    

交叉类型 &

  1. 定义

    两个类型中成员属性、函数签名、索引签名等的并集:

    在类型的属性上,交叉类型是两个或多个类型的并集;但在类型的概念上,交叉类型是两个或多个类型的交集,即既是类型A,又是类型B。

    interface Teenager{
     name: string;
     age: number;
    }
    interface Student{
     grade: number;
     id: string;
    }
    type Undergraduate = Person & Focusable;
    

    注意点:成员类型不能是多个原始类型:

    type T = string | number | boolean; // type T = never
    
  2. 常见作用

    • 可以通过将多个接口“合并”获得新的类型,便于在原有的接口结构上进行新定义:

      interface UserInfo {
        name: string;
        age: number;
      }
      
      interface PurchaseHistory {
        purchasedItems: string[];
        totalSpent: number;
      }
      
      // 使用交叉类型创建一个用户对象
      type User = UserInfo & PurchaseHistory;
      const user: User = {
        name: 'John',
        age: 30,
        purchasedItems: ['Shoes', 'Shirt', 'Hat'],
        totalSpent: 150
      };
      
    • 还可以在“合并”的基础上新增接口中的属性:

      // 新增需求形成新的类型vip
      type Vip = User & { id: string, level: number };
      const vip: VIP = {
        name: 'Ali',
        age: 22,
        purchasedItems: ['Shoes', 'Shirt'],
        totalSpent: 300,
        id: '001',
        level: 8
      }
      

常用操作符

通常在对类型的属性等进行操作时,这几个操作符会一起使用。

  1. typeof

    获取类型,作为一种类型返回:

    interface Person{
      name: string;
      age: number;
    }
    const ali: Person = { name: 'Ali', age: 21 };
    
    type Alis = typeof ali; // type Alis = Person;
    
  2. keyof

    获取所有键,作为联合类型返回:

    interface Person{
      name: string;
      age: number;
    }
    type key = keyof Person; // "name" | "age"
    
  3. in

    迭代对象中的元素,和js中的for...in​使用方法相似:

    type Keys = "a" | "b" | "c";
    type Obj = {
      [k in Keys]: any;
    }
    //Obj = { a: any, b: any, c: any }
    
  4. T[k]

    索引类型,类似于数组中按照索引取元素:

    interface Person {
      name: string;
      age: number;
    }
    let name: Person['name'] // let name: string
    

声明文件

使用declare​关键字进行变量类型声明的.d.ts​文件被称为声明文件:

// sample.d.ts
declare module 'some-library' {
  export function doSomething(arg: string): number;
  export const value: string;
}

这些文件不包含具体的实现代码,而是仅包含类型声明。通常用于在TypeScript项目中提供类型检查和代码补全的支持,使得在使用第三方JS库、模块或一些没有提供TS支持的库时,不会出现“缺少类型”的提示。通常声明文件存放在项目的根目录下。

一般第三方js插件在npm上都有对应的声明文件,可以通过@types/xxx​关键字进行引入:

npm i @types/jquery

泛型

工具类型

  1. Partial<T>

    让类型中的属性可选:

    type Partial<T> = {
      [P in keyof T]?: T[P];
    }
    
  2. Required<T>

    让类型中的属性必选:

    其中-?​表示属性必选。

    type Required<T> = {
      [P in keyof T]-?: T[P];
    }
    
  3. Readonly<T>

    让类型中的属性只可读:

    type Raedonly<T> = {
      readonly [P in keyof T]: T[P]
    }
    

使用举例

可以用于限制向后端接口发起请求的参数规范。如定义一个接口传参规范:

import axios from 'axios';

interface API {
  '/book/detail': {
     id: number,
  },
  '/book/comment': {
     id: number,
     comment: string
  }
}

// 接口与其参数的结构相对应
function request<T extends keyof API>(url: T, obj: API[T]){
  return axios.post(url, obj)
}

那么发起请求的接口和传入的参数结构不对应时就会出现报错:

// 路径错误
request('/book/test', {
  id: 123,
  comment: 'hello'
})

// 参数错误
request('/book/detail', {
  id: 123,
  comment: 'hello'
})