前段开发中不可避免的会与 API 沟通,理想情况下,我们可以在前端代码中直接定义 API 的数据类型,这样前端代码就可以直接使用,但是,理想归理想,实际过程中,API 返回的数据结构跟前端直接使用的数据结构尝尝不会完全相同,举个例子:
// API 返回的数据:
[
{
name: 'zhangsan',
sex: 'male',
},
]
// 前端定义类型:
interface User {
name: string,
gender: string,
}
这里就是第一个问题,往往会出现同一个字段的不同名字,出现的原因,当然可能是,前后端不同的命名习惯,不同的 API 系统或者 version, 我们如果为了方便,前段的类型完全按照 API 来定义就有点被牵着鼻子走了。当然,一个简单的方法是,我们可以写一个 convert 方法:
class User {
name: string;
gender: string;
constructor(json: any) {
Object.assign(this, json);
this.gender = json.sex;
}
toJson() {
return ({
...this,
sex: this.gender
});
}
}
当然,你甚至可以定义两个类型: apiUser, User, 然后手动维护两者的 map 关系。
最偷懒的,莫过于,API 是什么样子,类型就定义成什么样子。
刚刚的方案其实有一个问题,一是需要人每次做重复的工作,写一段粗看不知道为什么要写的逻辑,而是,本质上,是 a => b 名字的转换关系,而我们却不得不每次都要写 a=>b, b=>a 的手动转化。费时费力。
有没有一种可能性,我只需要描述,User 中,a 与 b 的对应关系,方法就可以自动帮助我做这样的转换。当然是可以的。
首先,我们知道,interface 在运行过程中是丢失的,只在编译过程中对 TS 有提示效果,编译成 JS 运行后就变成不可知的了。所以,我们基本上只能通过 class 来定义。那如何描述 class 上某个字段对应于 json 的某个字段呢?
熟悉 Angular 的话可能就很明白了,Decorators,它可以有效的帮助我们给 class, function, property 添加额外信息。举个例子:
class User {
name: string;
@bindJsonProp('sex')
gender: string;
}
这样,我们只需要增加一行,就可以描述清楚,gender 和 sex 的对应关系。剩下的转换关系,我们都可以交给一个独立的方法来做:
const user = convertToType(User)(jsonData);
// ...
// ...
const jsonData = convertToJson(user);
当然,我们甚至可以做类型的转换,比如:
API 中 Date 为 string,我们要转化为 string:
class User {
name!: string;
@bindJsonProp({ name: 'sex'})
gender!: string;
@bindJsonProp({
typeConverter: dateStringConverter
})
date!: Date;
}
感兴趣的可以看一下这里的实现: github.com/winfa/ts-js…
以上。