读代码时候看到一下类型,原来是Typescript中高级类型Record

typescript 常用组件(Typescript高级类型Record)(1)

ts文档上对Record的介绍不多,但却经常用到,Record是一个很好用的工具类型。

Record<K,T>构造具有给定类型T的一组属性K的类型。在将一个类型的属性映射到另一个类型的属性时,Record非常方便。

他会将一个类型的所有属性值都映射到另一个类型上并创造一个新的类型.

示例:

interface EmployeeType { id: number fullname: string role: string } let employees: Record<number, EmployeeType> = { 0: { id: 1, fullname: "John Doe", role: "Designer" }, 1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" }, 2: { id: 3, fullname: "Sara Duckson", role: "Developer" }, } // 0: { id: 1, fullname: "John Doe", role: "Designer" }, // 1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" }, // 2: { id: 3, fullname: "Sara Duckson", role: "Developer" }

Record的工作方式相对简单。在这里,它期望数字作为类型,属性值的类型是EmployeeType,因此具有id,fullName和role字段的对象。

再看下Record的源码。

/** * Construct a type with a set of properties K of type T */ type Record<K extends keyof any, T> = { [P in K]: T; };

好像源码也比较简单,即将K中的每个属性([P in K]),都转为T类型。常用的格式如下:

type proxyKType = Record<K,T>

会将K中的所有属性值都转换为T类型,并将返回的新类型返回给proxyKType,K可以是联合类型、对象、枚举…

我们再看几个demo验证下:

//demo1 type petsGroup = 'dog' | 'cat' | 'fish'; interface IPetInfo { name:string, age:number, } type IPets = Record<petsGroup, IPetInfo>; const animalsInfo:IPets = { dog:{ name:'dogName', age:2 }, cat:{ name:'catName', age:3 }, fish:{ name:'fishName', age:5 } }

可以看到 IPets 类型是由 Record<petsGroup, IPetInfo>返回的。将petsGroup中的每个值(‘dog’ | ‘cat’ | ‘fish’)都转为 IPetInfo 类型。

当然也可以自己在第一个参数后追加额外的值,如下面:

//demo2 type petsGroup = 'dog' | 'cat' | 'fish'; interface IPetInfo { name:string, age:number, } type IPets = Record<petsGroup | 'otherAnamial', IPetInfo>; const animalsInfo:IPets = { dog:{ name:'dogName', age:2 }, cat:{ name:'catName', age:3 }, fish:{ name:'fishName', age:5 }, otherAnamial:{ name:'otherAnamialName', age:10 } }

可以看到在demo1的基础上,demo2在

type IPets = Record<petsGroup | ‘otherAnamial’, IPetInfo>; 中除了petsGroup的值之外,还追加了 'otherAnamial’这个值。

下面看一个略复杂的例子,用axios将http的几个请求封装一下,使用Record定义每个请求方法的形状。

enum IHttpMethods { GET = 'get', POST = 'post', DELETE = 'delete', PUT = 'put', } const methods = ["get", "post", "delete", "put"]; interface IHttpFn { <T = any>(url: string, config?: AxiosRequestConfig): Promise<T> } type IHttp = Record<IHttpMethods, IHttpFn>; const httpMethods: IHttp = methods.reduce((map: any, method: string) => { map[method] = (url: string, options: AxiosRequestConfig = {}) => { const { data, ...config } = options; return (axios as any)[method](url, data, config) .then((res: AxiosResponse) => { if (res.data.errCode) { //todo somethins } else { //todo somethins } }); } return map }, {}) export default httpMethods;

上面这个demo就先枚举除了几个常见的http请求的方法名,而每个方法都接受请求的url以及可选参数config,然后每个方法返回的都是一个Promise。这种业务常见使用Record再合适不过了。使用下面的方式定义了每个方法的形状。

type IHttp = Record<IHttpMethods, IHttpFn>;

最后只需要遍历一下几个方法,对每个方法有各自的具体实现即可。这里是用了reduce的特性,遍历了一下数据,然后将所有的方法体放在一个对象中,最终结果用 httpMethods接受,再将httpMethods对外暴露出去,那么外面就可直接调用了。这里把一些业务的部分抽离出去了(比如设置请求头、设置token之类的),只是为了简单说明一个比较合适使用Record的业务场景。

在复杂业务场景下这是一个很好用的特性,比如我们在cocos creator engine 源码中

typescript 常用组件(Typescript高级类型Record)(2)

,