参考文章:

  1. 20个内置泛型接口助你事半功倍——TypeScript系列进阶篇:(一) TS内置泛型接口
  2. TypeScript系列 进阶篇:(三) 声明合并

内置泛型接口

可选类型接口Partial

可选类型接口 Partial 可以通过将传入的类型的所有属性变为可选属性,从而得到一个新的类型。

示例:

  1. interface ResType {
  2. success: boolean;
  3. data: Record<string, string>;
  4. }
  5. // 在Partial作用下,原本必需的data属性变为可选的
  6. const res: Partial<ResType> = {
  7. success: false,
  8. };

必需类型接口Required

与 Partial 相反,Required 可以将接口中的属性全部设置为必需:

  1. interface ResType {
  2. success: boolean;
  3. data?: Record<string, string>;
  4. }
  5. let res: Required<ResType>;
  6. // 编译报错,缺少必需属性data
  7. res = {
  8. success: false,
  9. };

只读类型接口Readonly

Readonly 用于将接口的所有属性设置为只读的:

  1. interface ResType {
  2. success: boolean;
  3. data?: Record<string, string>;
  4. }
  5. const res: Readonly<ResType> = {
  6. success: true,
  7. }
  8. // 编译报错,因为此时success为只读属性
  9. res.success = false;

Record

创建一个 key 为 Key 类型、value 为 Type 类型的对象类型。

  1. interface ResType {
  2. success: boolean;
  3. data: Record<string, string>;
  4. }
  5. // res.data就是一个键值均为string类型的对象
  6. const res: ResType = {
  7. success: true,
  8. data: {
  9. name: 'A',
  10. },
  11. };

Pick

挑选出 Type类型 中的 Keys 类型的属性,得到一个新的类型。

  1. interface ResType {
  2. success: boolean;
  3. data?: Record<string, string>;
  4. errMsg: string;
  5. }
  6. // res的类型仅包含ResType的success和data两个属性,加上errMsg属性反而会报错
  7. const res: Pick<ResType, 'success' | 'data'> = {
  8. success: true,
  9. data: {
  10. name: 'A',
  11. },
  12. }

Omit

与 Pick 相反,Omit 会剔除掉 Keys 指定的属性:

  1. interface ResType {
  2. success: boolean;
  3. data?: Record<string, string>;
  4. errMsg: string;
  5. }
  6. // Omit的作用下,res只能包含errMsg属性
  7. const res: Omit<ResType, 'success' | 'data'> = {
  8. errMsg: '',
  9. }

Exclude

从联合类型 UnionType 中移除某些类型,得到一个新的类型。

以处理系统 Code 为例:

  1. type SystemCode = 'PARAMS_ERROR' | 'SYSTEM_ERROR' | 'SUCCESS';
  2. interface BaseResType {
  3. success: boolean;
  4. data: Record<string, string>;
  5. errCode: Exclude<SystemCode, 'SUCCESS'>; // errCode为SystemCode中除"SUCCESS"外的联合类型
  6. errMsg: string;
  7. }
  8. type ErrorResType = Pick<BaseResType, 'success' | 'errCode' | 'errMsg'>;
  9. const res: ErrorResType = {
  10. success: false,
  11. errCode: 'PARAMS_ERROR',
  12. errMsg: '',
  13. };

Extract

提取出 Type 类型中能符合 Union 联合类型的类型,得到一个新的类型。

  1. // 假设有新旧两套接口,旧版success值为number类型(0和1),新版值为boolean类型
  2. type SuccessType = boolean | number;
  3. interface BaseResType {
  4. success: SuccessType;
  5. data: Record<string, string>;
  6. errCode: Exclude<SystemCode, 'SUCCESS'>;
  7. errMsg: string;
  8. }
  9. // 新版请求成功的响应体如下,将success属性矫正为boolean类型
  10. // 当然,你也可以直接将其设置为boolean,这里只是为了演示效果
  11. interface NewBaseResType extends BaseResType {
  12. success: Extract<SuccessType, boolean>;
  13. }

Parameters

提取函数类型(或 any,never 等类型)中的参数,得到一个新的元组类型 (或 never)。

  1. // ...
  2. type FetchType<T = any, R = any> = (path: string, type: string, params: T) => BaseResType<R>;
  3. type FetchParams = Parameters<FetchType>; // FetchParams为FetchType的入参数组
  4. const params: FetchParams = [
  5. '/api/home',
  6. 'post',
  7. { v: 1 },
  8. ];

ReturnType

得到一个由函数类型的返回值类型组成的新类型。

  1. // ...
  2. type FetchType<T = any, R = any> = (path: string, type: string, params: T) => SuccessResType<R>;
  3. type FetchReturn<R = any> = ReturnType<FetchType<R>>; // FetchReturn等同于SuccessResType
  4. const res: FetchReturn = {
  5. success: true,
  6. data: {},
  7. };

声明合并

TypeScript 中的声明,有以下三种形式:

  1. 命名空间:命名空间的声明,用到 namespace 关键字;
  2. 类型:类型声明,用到 type 或 interface 关键字;
  3. 值:值声明,如 var、let、const 等用于声明一个变量,class、enum、function 等也属于值声明的范畴。

合并interface

interface 的合并是最简单也是最常见的声明合并。如果一个文件中有多个同名 interface,TypeScript 会自动将这些同名 interface 合并起来,但是需要注意,如果其中有同名的非函数字段,则该字段对应的类型也应该相同,否则会被标记为错误

  1. interface TaskType {
  2. id: string;
  3. }
  4. interface TaskType {
  5. name: string;
  6. }
  7. // 以下interface的id字段与其他同名interface的声明存在冲突
  8. // interface TaskType {
  9. // id: number;
  10. // }
  11. // TaskType有id和name两个必需字段
  12. const task: TaskType = {
  13. id: 'CLEAN_ROOMS',
  14. name: '打扫房间',
  15. };

合并namespace

开始本节前需要先了解下命名空间 namespace,可以浏览我之前写的一篇关于 namespace 的文章:TypeScript学习笔记——命名空间

和 interface 的合并类似,声明的同名 namespace 也会合并每个声明的成员。由于 namespace 会创建 namespace 和值,命名空间和值都会合并。

  1. namespace IM {
  2. const version = '1.0.0';
  3. export const remark = 'WebUnion IM SDK';
  4. export interface AccountType {
  5. id: string;
  6. name: string;
  7. }
  8. export class Account {
  9. data: AccountType;
  10. constructor(name: string) {
  11. this.data = {
  12. id: new Date().valueOf().toString(),
  13. name,
  14. };
  15. }
  16. }
  17. }
  18. namespace IM {
  19. export enum SessionEnum {
  20. P2P = 'P2P',
  21. TEAM = 'TEAM',
  22. }
  23. export interface SessionType {
  24. id: string;
  25. type: keyof SessionEnum;
  26. }
  27. export class Session {
  28. data: SessionType;
  29. constructor(id: string, type: keyof SessionEnum) {
  30. this.data = { id, type };
  31. }
  32. }
  33. }

第一个 IM 命名空间中包含用户(Account)相关接口和类,第二个 IM 命名空间中包含会话(Session)相关的接口和类,两个命名空间能够顺利合并,包含两个命名空间所有内容。

接着我们再编写第三个同名命名空间,内容如下:

  1. namespace IM {
  2. // version并没有export,所以此处访问version会报错
  3. export const getVersion = () => {
  4. console.log(version);
  5. }
  6. // remark有export,可以正常访问
  7. export const getRemark = () => {
  8. console.log(remark);
  9. }
  10. }

getVersion()getRemark()分别用于打印 IM.version 和 IM.remark,但是一个报错一个正常,区别就在于访问的对象是否用 export 导出。

namespace与class、function、enum等合并

以下是一些特殊的声明合并场景:

  1. 同名 class 与 namespace 合并,namespace 导出的成员会成为同名 class 中的静态成员,没有导出的成员则被忽略。 ```typescript class IM { author = ‘WJT20’; }

namespace IM { const version = ‘1.0.0’; export const remark = ‘WebUnion IM SDK’; }

const im = new IM(); console.log(im.author); // “WJT20” console.log(im.version); // 报错 console.log(IM.version); // 报错 console.log(IM.remark); // “WebUnion IM SDK”

  1. 可以理解为最终生成的是以下 class
  2. ```typescript
  3. class IM {
  4. author = 'WJT20';
  5. static remark = 'WebUnion IM SDK';
  6. }
  1. 同名 function 与 namespace 合并,namespace 导出的成员会成为同名 function 的属性,没有导出的成员则被忽略。 ```typescript function IM() {}

namespace IM { const version = ‘1.0.0’; export const remark = ‘WebUnion IM SDK’; }

// 输出: [Function: IM] { remark: ‘WebUnion IM SDK’ },比合并前多了后半部分 console.log(IM);

  1. 3. 同名 enum namespace 合并,namespace 导出的成员会成为同名 enum 的**扩充**,没有导出的成员则被忽略。
  2. ```typescript
  3. enum IM {}
  4. namespace IM {
  5. const version = '1.0.0';
  6. export const remark = 'WebUnion IM SDK';
  7. export const getRemark = () => {
  8. console.log(version, remark);
  9. }
  10. }
  11. IM.getRemark(); // “1.0.0 WebUnion IM SDK”
  12. console.log(IM.remark); // “WebUnion IM SDK”