本篇内容主要讲解“Typescript怎么使用装饰器实现接口字段映射与Mock”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Typescript怎么使用装饰器实现接口字段映射与Mock”吧!
需求
最核心的问题就是要达到:接口字段的修改不能影响项目中实际使用的字段,无论是字段名的修改还是类型的修改
这里考虑使用装饰器附带额外信息,主要是接口字段信息,与需要转换的类型
既然可以转换类型了,考虑把字段 “翻译” 功能加上
既然能转换了,能就再加个 Mock 吧,摆脱开发过程中对后端接口的依赖
设计
语言:typescript 构建工具:rollup 自动化测试:jest 代码规范:eslint + prettier 提交规范:commitlint
Decorator
首先,我们需要一个对象
是这个对象 {}
class Lesson { public name: string; public teacher: string; public datetime: string; public applicants: number; public compulsory: boolean; constructor() { this.name = ""; this.teacher = ""; this.datetime = ""; this.compulsory = false; }}
上面的代码,就是我们构造出的 Lesson 类,它的属性字段就是我们会在项目中实际使用的字段
现在我们需要把这个类的属性字段与接口返回的字段对应上,这时候就需要用到 装饰器 了,随便取个名字,我这里是用 mapperProperty
,接收两个参数,第一个是接口返回的字段名,第二个是期望最终得到的类型(不是接口字段本身的类型)
class Lesson { @mapperProperty("ClassName", "string") public name: string; @mapperProperty("TeacherName", "string") public teacher: string; @mapperProperty("DateTime", "datetime") public datetime: string; @mapperProperty("ApplicantNumber", "int") public applicants: number; @mapperProperty("Compulsory", "boolean") public compulsory: boolean; constructor() { this.name = ""; this.teacher = ""; this.datetime = ""; this.date = ""; this.time = ""; this.compulsory = false; }}
如上面的代码,我们给每个属性字段都加上了装饰器,并告知了接口中对应的字段名称,以及我们希望得到的类型。 例如代码中的 applicants 字段,对应了接口中的 ApplicantNumber 字段,无论接口返回的是字符串还是数值类型,我们都希望最终得到的是 int 类型(指代整数)的数据
接下来要把接口字段名称与我们期望得到的类型先缓存起来
这里我们借助 Reflect Metadata
实现缓存
示例代码如下
function mapperProperty(apiField, type) { Reflect.metadata("key", { apiField, // 接口字段名 type, // 期望类型 });}
Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据;我们使用 reflect-metadata
来模拟该功能
Transform
有了接口字段名与期望的类型,接下来的转换就简单了
第一步,先读取上一步缓存的元数据信息
const instance = new Lesson();const meta = Reflect.getMetadata("key", instance, "applicants");console.log(meta);
这里的 key 即元数据的键,上面的代码是读取 Lesson 类中 applicants 字段的元数据,meta 打印的结果如下
{ apiField: 'ApplicantNumber', type: 'int'}
第二步,转换
function deserialize(clazz, json) { const instance = new clazz(); const meta = Reflect.getMetadata("key", instance, "applicants"); const { apiField, type } = meta; const ori = json[apiField]; // json 为接口返回的数据 let value; switch (type) { case "int": value = parseInt(ori, 10); break; // 其它类型转换 } // 后续处理}
到这基本就实现了最核心的能力,只要愿意可以扩展更多类型,欢迎一起来完善
Object and Array
对象与数组的转换与基本类型的转换大差不差,这里我将对象、数组的装饰器命名为 deepMapperProperty
,只需将第二个参数的类型,改为接收一个类即可
示例代码如下
function deepMapperProperty(apiField, clazz) { Reflect.metadata("key", { apiField, // 接口字段名 clazz, // 子级 });}
取值方式同上,不再赘述了,只需改一下转换的代码
转换对象的示例代码如下,递归调用一下即可
const { clazz } = meta;if (clazz) { value = deserialize(clazz, value);}
数组则直接使用 map 遍历
function deserializeArr(clazz, list) { return list.map((ele) => deserialize(clazz, ele));}
Mock
模拟数据部分,是直接返回的前端项目中使用的字段,而非修改接口字段的返回值
实现模拟数据拢共分三步:
与转换同样的步骤,要先读取字段的期望类型,这里只需要类型即可
遍历读取类中各个字段的元数据,得到各个字段的期望类型
根据期望类型使用不同的随机函数,生成相应类型的数据,这里我封装了三种类型的随机函数
获取随机整数
获取随机字符串
获取随机小数
针对对象与数组特殊处理
对象:这个简单,老规矩,递归解决
数组:数组需要先随机生成一下数组长度,再使用 map 遍历,递归调用一下 mock 函数
使用
安装
npm i type-json-mapper
属性装饰器
内置三种类属性装饰器:
@mapperProperty(apiField, type)
基本数据类型使用该装饰器
接收两个参数:
apiField:接口字段名
type:字段转换类型(可选值:string | int | flot | boolean | date | time | datetime)
@deepMapperProperty (apiField, Class)
对象/数组使用该装饰器
接收两个参数:
apiField:接口字段名
Class:类
@filterMapperProperty(apiField, filterFunc)
自定义过滤器(翻译)使用该装饰器
接收两个参数:
apiField:接口字段名
filterFunc:自定义过滤器函数
const filterFunc = (value) => { return "translated text";};
方法
deserialize(Clazz, json)
反序列化 json 对象
Clazz:类
json:接口返回的对象数据
deserializeArr(Clazz, list)
反序列化数组
Clazz:类
list:接口返回的数组数据
mock(Clazz, option)
生成模拟数据
Clazz:类
option:mock 配置
mock 配置
名称 | 类型 | 描述 | 默认值 |
---|---|---|---|
fieldLength | Object | 字段长度 | - |
arrayFields | string[] | 数组类型字段 | - |
fieldLength
数据类型 | length 含义 |
---|---|
string | 字符串长度 |
int | 最大整数 |
float | 字符长度(保留两位小数) |
例:
class Student { @mapperProperty("StudentID", "string") public id: string; @mapperProperty("StudentName", "string") public name: string; @mapperProperty("StudentAge", "int") public age: number; @mapperProperty("Grade", "float") public grade: number; constructor() { this.id = ""; this.name = ""; this.age = 0; this.grade = 0; }}mock(Student, { fieldLength: { age: 20, grade: 4, name: 6 } });
使用示例
这里预先造了几个类,并给类属性加上了装饰器
import { mapperProperty, deepMapperProperty, filterMapperProperty,} from "type-json-mapper";class Lesson { @mapperProperty("ClassName", "string") public name: string; @mapperProperty("Teacher", "string") public teacher: string; @mapperProperty("DateTime", "datetime") public datetime: string; @mapperProperty("Date", "date") public date: string; @mapperProperty("Time", "time") public time: string; @mapperProperty("Compulsory", "boolean") public compulsory: boolean; constructor() { this.name = ""; this.teacher = ""; this.datetime = ""; this.date = ""; this.time = ""; this.compulsory = false; }}class Address { @mapperProperty("province", "string") public province: string; @mapperProperty("city", "string") public city: string; @mapperProperty("full_address", "string") public fullAddress: string; constructor() { this.province = ""; this.city = ""; this.fullAddress = ""; }}// 状态映射关系const stateMap = { "1": "读书中", "2": "辍学", "3": "毕业" };class Student { @mapperProperty("StudentID", "string") public id: string; @mapperProperty("StudentName", "string") public name: string; @mapperProperty("StudentAge", "int") public age: number; @mapperProperty("StudentSex", "string") public sex: string; @mapperProperty("Grade", "float") public grade: number; @deepMapperProperty("Address", Address) public address?: Address; @deepMapperProperty("Lessons", Lesson) public lessons?: Lesson[]; @filterMapperProperty("State", (val: number) => stateMap[`${val}`]) public status: string; @filterMapperProperty("Position", (val: number) => stateMap[`${val}`]) public position: string; public extra: string; constructor() { this.id = ""; this.name = ""; this.age = 0; this.sex = ""; this.grade = 0; this.address = undefined; this.lessons = undefined; this.status = ""; this.position = ""; this.extra = ""; }}
以下是接口返回的数据:
const json = [ { StudentID: "123456", StudentName: "李子明", StudentAge: "10", StudentSex: 1, Grade: "98.6", Address: { province: "广东", city: "深圳", full_address: "xxx小学三年二班", }, Lessons: [ { ClassName: "中国上下五千年", Teacher: "建国老师", DateTime: 1609430399000, Date: 1609430399000, Time: 1609430399000, Compulsory: 1, }, { ClassName: "古筝的魅力", Teacher: "美丽老师", DateTime: "", }, ], State: 1, Position: 123, extra: "额外信息", }, { StudentID: "888888", StudentName: "丁仪", StudentAge: "18", StudentSex: 2, Grade: null, Address: { province: "浙江", city: "杭州", full_address: "xxx中学高三二班", }, Lessons: [], State: 2, },];
开始转换,因接口返回的是数组,这里使用 deserializeArr
import { deserializeArr } from "type-json-mapper";try { const [first, second] = deserializeArr(Student, json); console.log(first); console.log(second);} catch (err) { console.error(err);}
输出结果如下
// first
{
id: "123456",
name: "李子明",
age: 10,
sex: "1",
grade: 98.6,
address: { province: "广东", city: "深圳", fullAddress: "xxx小学三 年二班" },
lessons: [
{
name: "中国上下五千年",
teacher: "建国老师",
datetime: "2020-12-31 23:59:59",
date: "2020-12-31",
time: "23:59:59",
compulsory: true,
},
{
name: "古筝的魅力",
teacher: "美丽老师",
datetime: "",
date: undefined,
time: undefined,
compulsory: undefined,
},
],
status: "读书中",
position: 123,
extra: "额外信息",
};
// second
{
id: "888888",
name: "丁仪",
age: 18,
sex: "2",
grade: null,
address: { province: "浙江", city: "杭州", fullAddress: "xxx中学高三二班" },
lessons: [],
status: "辍学",
position: undefined,
extra: undefined,
};
如果后端接口还没开发完成,我们还可以直接 mock
import { mock } from "type-json-mapper";const res = mock(Student, { fieldLength: { age: 20, grade: 4, name: 6 }, arrayFields: ["lessons"],});console.log(res);
输出结果如下
{
id: 'QGBLBA', name: 'KTFH6d',
age: 4,
sex: 'IINfTm',
grade: 76.15,
address: { province: 'qvbCte', city: 'DbHfFZ', fullAddress: 'BQ4uIL' },
lessons: [
{
name: 'JDtNMx',
teacher: 'AeI6hB',
datetime: '2023-2-18 15:00:07',
date: '2023-2-18',
time: '15:00:07',
compulsory: true
},
{
name: 'BIggA8',
teacher: '8byaId',
datetime: '2023-2-18 15:00:07',
date: '2023-2-18',
time: '15:00:07',
compulsory: false
},
{
name: 'pVda1n',
teacher: 'BPCmwa',
datetime: '2023-2-18 15:00:07',
date: '2023-2-18',
time: '15:00:07',
compulsory: false
}
],
status: '',
position: '',
extra: ''
}
到此,相信大家对“Typescript怎么使用装饰器实现接口字段映射与Mock”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!