参考文章:
内置泛型接口
可选类型接口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>;
// 编译报错,缺少必需属性data
res = {
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等同于SuccessResType
const 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:
```typescript
class 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 的**扩充**,没有导出的成员则被忽略。
```typescript
enum 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”