参考文章:
内置泛型接口
可选类型接口Partial
可选类型接口 Partial 可以通过将传入的类型的所有属性变为可选属性,从而得到一个新的类型。
示例:
interface ResType {success: boolean;data: Record<string, string>;}// 在Partial作用下,原本必需的data属性变为可选的const res: Partial<ResType> = {success: false,};
必需类型接口Required
与 Partial 相反,Required 可以将接口中的属性全部设置为必需:
interface ResType {success: boolean;data?: Record<string, string>;}let res: Required<ResType>;// 编译报错,缺少必需属性datares = {success: false,};
只读类型接口Readonly
Readonly 用于将接口的所有属性设置为只读的:
interface ResType {success: boolean;data?: Record<string, string>;}const res: Readonly<ResType> = {success: true,}// 编译报错,因为此时success为只读属性res.success = false;
Record
创建一个 key 为 Key 类型、value 为 Type 类型的对象类型。
interface ResType {success: boolean;data: Record<string, string>;}// res.data就是一个键值均为string类型的对象const res: ResType = {success: true,data: {name: 'A',},};
Pick
挑选出 Type类型 中的 Keys 类型的属性,得到一个新的类型。
interface ResType {success: boolean;data?: Record<string, string>;errMsg: string;}// res的类型仅包含ResType的success和data两个属性,加上errMsg属性反而会报错const res: Pick<ResType, 'success' | 'data'> = {success: true,data: {name: 'A',},}
Omit
与 Pick 相反,Omit 会剔除掉 Keys 指定的属性:
interface ResType {success: boolean;data?: Record<string, string>;errMsg: string;}// Omit的作用下,res只能包含errMsg属性const res: Omit<ResType, 'success' | 'data'> = {errMsg: '',}
Exclude
从联合类型 UnionType 中移除某些类型,得到一个新的类型。
以处理系统 Code 为例:
type SystemCode = 'PARAMS_ERROR' | 'SYSTEM_ERROR' | 'SUCCESS';interface BaseResType {success: boolean;data: Record<string, string>;errCode: Exclude<SystemCode, 'SUCCESS'>; // errCode为SystemCode中除"SUCCESS"外的联合类型errMsg: string;}type ErrorResType = Pick<BaseResType, 'success' | 'errCode' | 'errMsg'>;const res: ErrorResType = {success: false,errCode: 'PARAMS_ERROR',errMsg: '',};
Extract
提取出 Type 类型中能符合 Union 联合类型的类型,得到一个新的类型。
// 假设有新旧两套接口,旧版success值为number类型(0和1),新版值为boolean类型type SuccessType = boolean | number;interface BaseResType {success: SuccessType;data: Record<string, string>;errCode: Exclude<SystemCode, 'SUCCESS'>;errMsg: string;}// 新版请求成功的响应体如下,将success属性矫正为boolean类型// 当然,你也可以直接将其设置为boolean,这里只是为了演示效果interface NewBaseResType extends BaseResType {success: Extract<SuccessType, boolean>;}
Parameters
提取函数类型(或 any,never 等类型)中的参数,得到一个新的元组类型 (或 never)。
// ...type FetchType<T = any, R = any> = (path: string, type: string, params: T) => BaseResType<R>;type FetchParams = Parameters<FetchType>; // FetchParams为FetchType的入参数组const params: FetchParams = ['/api/home','post',{ v: 1 },];
ReturnType
得到一个由函数类型的返回值类型组成的新类型。
// ...type FetchType<T = any, R = any> = (path: string, type: string, params: T) => SuccessResType<R>;type FetchReturn<R = any> = ReturnType<FetchType<R>>; // FetchReturn等同于SuccessResTypeconst res: FetchReturn = {success: true,data: {},};
声明合并
TypeScript 中的声明,有以下三种形式:
- 命名空间:命名空间的声明,用到 namespace 关键字;
- 类型:类型声明,用到 type 或 interface 关键字;
- 值:值声明,如 var、let、const 等用于声明一个变量,class、enum、function 等也属于值声明的范畴。
合并interface
interface 的合并是最简单也是最常见的声明合并。如果一个文件中有多个同名 interface,TypeScript 会自动将这些同名 interface 合并起来,但是需要注意,如果其中有同名的非函数字段,则该字段对应的类型也应该相同,否则会被标记为错误。
interface TaskType {id: string;}interface TaskType {name: string;}// 以下interface的id字段与其他同名interface的声明存在冲突// interface TaskType {// id: number;// }// TaskType有id和name两个必需字段const task: TaskType = {id: 'CLEAN_ROOMS',name: '打扫房间',};
合并namespace
开始本节前需要先了解下命名空间 namespace,可以浏览我之前写的一篇关于 namespace 的文章:TypeScript学习笔记——命名空间。
和 interface 的合并类似,声明的同名 namespace 也会合并每个声明的成员。由于 namespace 会创建 namespace 和值,命名空间和值都会合并。
namespace IM {const version = '1.0.0';export const remark = 'WebUnion IM SDK';export interface AccountType {id: string;name: string;}export class Account {data: AccountType;constructor(name: string) {this.data = {id: new Date().valueOf().toString(),name,};}}}namespace IM {export enum SessionEnum {P2P = 'P2P',TEAM = 'TEAM',}export interface SessionType {id: string;type: keyof SessionEnum;}export class Session {data: SessionType;constructor(id: string, type: keyof SessionEnum) {this.data = { id, type };}}}
第一个 IM 命名空间中包含用户(Account)相关接口和类,第二个 IM 命名空间中包含会话(Session)相关的接口和类,两个命名空间能够顺利合并,包含两个命名空间所有内容。
接着我们再编写第三个同名命名空间,内容如下:
namespace IM {// version并没有export,所以此处访问version会报错export const getVersion = () => {console.log(version);}// remark有export,可以正常访问export const getRemark = () => {console.log(remark);}}
getVersion()和getRemark()分别用于打印 IM.version 和 IM.remark,但是一个报错一个正常,区别就在于访问的对象是否用 export 导出。
namespace与class、function、enum等合并
以下是一些特殊的声明合并场景:
- 同名 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”
可以理解为最终生成的是以下 class:```typescriptclass IM {author = 'WJT20';static remark = 'WebUnion IM SDK';}
- 同名 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);
3. 同名 enum 与 namespace 合并,namespace 导出的成员会成为同名 enum 的**扩充**,没有导出的成员则被忽略。```typescriptenum IM {}namespace IM {const version = '1.0.0';export const remark = 'WebUnion IM SDK';export const getRemark = () => {console.log(version, remark);}}IM.getRemark(); // “1.0.0 WebUnion IM SDK”console.log(IM.remark); // “WebUnion IM SDK”
