我的项目地址查看https://github.com/goblin-pitcher/data-adapter
背景
开发过程中往往须要升高对后端数据结构的依赖性,防止接口数据结构扭转引起的前端代码大面积批改。因而须要一个适配器工具,并对转换规则的对立治理,当接口数据结构扭转时,只需动静保护转换规则即可。
需要剖析
首先剖析IO,须要实现的办法如下
/**
* @params {Object} data 须要转换的数据
* @params rules 数据结构待定,转换规则
* @params {Object} options 转换配置
* @returns {Object} adaptedData 返回数据
*/
function adapter(data, rules, options) {}
因为返回的数据量可能很大,为防止不必要的开销,最好在原地批改数据,因而返回的adaptedData
满足adaptedData === data
。若确定数据量不大,且须要返回新的数据时,最好在应用时传入cloneDeep(data)
。
旧版本
最后版是我的项目长期需要的产物,转换规则rules是一个key为匹配门路,value为转换后的门路或值,具体代码可参考地址,示例如下:
/**
* key为匹配门路, 如:
* 'e|a'代表匹配key为e或a,
* 'b.c'代表匹配门路['b', 'c']
* '/^c/'代表正则匹配key值,匹配规定为/^c/
* 写法上是反对多种类型混用,如'e|a.b./^c/',会匹配obj[e|a][b][/^c/]
* value可示意转换后的门路或值,规定如下:
* 当value为字符串待变转换后的门路
* 当value为办法时代表转换后的值,参数别离为:
* data: 匹配门路的值
* path: 匹配门路
* obj: 原对象
*/
const rules= {
"e|a": "b.a", // 将obj.e或obj.a的值放在obj.b.a下
"b.c": "b.d", // obj.b.c的值放在obj.b.d下
"/^c/": "b.f", // 将obj下以c结尾的key放到obj.b.f下
"b.ff": "b.g.f",
e: (data, path, obj) => obj.a + obj.ca, // obj.e = obj.a + obj.ca
"b.c": (data) => data ** 2, // obj.b.c = obj.b.c ** 2
};
const obj = {a:5,b:{g:{f:"xxx"},a:5,d:7,f:9},ca:8,cd:9}
adapter(obj, rules)
旧版本因为只是长期计划,无疑有很多问题。
规定定义上:比方b.c
代表门路['b', 'c']
,这会和作为key值的b.c
产生歧义,另外用字符串生成正则,写法上须要许多额定的本义符。
拓展性上:旧版本只反对一个配置,即retain—— 是否保留转换前的项。这部分代码写的比拟仓促,耦合性太强,增加新的配置须要批改很多个中央,不不便拓展。
新版本
首先在rules的定义上,为了防止旧版本的诸多问题,新版本采纳了Map作为规定。key为匹配门路,value为转换的规定。
应用形式
具体可参考测试示例
npm i git+https://github.com/goblin-pitcher/data-adapter.git -S
--------
import adapter from 'data-adapter';
const obj = {
a:5,
b:{
g:{
f:"xxx"
},
a:5,
}
}
const rules = new Map([
['a', 'transKey-a'],
[['b', /a|g/, 'f'], (path, value)=>`transKey-${path[path.length - 1]}`]
])
// 转换后数据格式如下:
// {
// 'transKey-a':5,
// b:{
// g:{
// 'transKey-f':"xxx"
// },
// a:5,
// }
// }
adapter(obj, rules)
adapter办法格局如下:
interface IOptions {
retain?: boolean;
transValue?: boolean;
matchFullRules?: boolean;
relativePath?: boolean,
priority?: ('string' | 'regExp' | 'function')[];
}
// 当options为布尔类型时,代表配置{retain: options}
type RulesAndOptions = [rules: Rules, options: boolean | IOptions];
interface Adapter {
(obj: Record<string, unknown>, ...args: RulesAndOptions): Record<string, unknown>;
(obj: Record<string, unknown>, ...args: RulesAndOptions[]): Record<string, unknown>;
}
// adapter也能够接管多个转换规则,即adapter(data, [rules1, rules2, ....])
匹配规定
定义匹配规定rules为Map构造。假如rules值如下:
const testFunc = (path, value, matchPath, matchRule) => path[path.length - 1]==='f' && value>5;
const rules = new Map([
[['b', /a|g/, testFunc], (path, value, matchPath, matchRule)=>`transKey-${path[path.length - 1]}`]
])
若以rules去转换data,其中一条rule的key是['b', /a|g/, testFunc]
,代表先匹配data.b
,而后寻找data.b.a
和data.b.g
,并别离寻找data.b.a
和data.b.g
下满足testFunc
的项,若该项存在,则将其key转换为transKey-${key}
配置阐明
interface IOptions {
// 是否保留转换前的数据,默认为false
retain?: boolean;
// rule.value是否作为转换项的值,默认为false
// 假如某条规定为new Map([['a', 'b']]):
// 1. 若该项为true,代表data.a = 'b'
// 2. 该项为false,代表data.b = data.a
transValue?: boolean;
// 是否匹配全门路,默认为true。
// 比方某条规定为new Map([[['a', 'b'], 'xxx']]),假如data.a.b不存在:
// 1. 当matchFullRules为true,则该条规定不失效
// 2. 当matchFullRules为false,则会退而求其次寻找data.a,若data.a存在,则会转换data.a
matchFullRules?: boolean;
// 转换后的门路是否绝对于转换前的门路,默认为false.
// 比方某条规定为new Map([[['a', 'b'], 'xxx']]):
// 1. 当relativePath为true,代表将data.a.b的值放到data.a.xxx下
// 2. 当relativePath为false, 代表将data.a.b的值放到data.xxx下
relativePath?: boolean,
// 匹配优先级,默认为['string', 'regExp', 'function']
// 比方某条规定为new Map([[['a', ['b', /^b/, testFunc]], 'xxx']])
// 其中['b', /^b/, testFunc]代表以多种规定去匹配data.a下的所有项,priority代表匹配的优先级
priority?: ('string' | 'regExp' | 'function')[];
}
实现思路
旧版本因为工夫比拟紧,过后程度也比拟差,实现挺乱的,拓展性也差。重构后以更正当的数据结构实现该性能。
假如规定数据如下:
const testFunc = (path, value) => path[path.length-1].endsWith('b')
const rules = new Map([[[/a|e/, ['b', /^b/, testFunc], 'xxx'], 'transValue']]);
const data = {
a: {
b: {
xxx: 7
},
ab: {abc: 4},
},
b: 5,
e: {acb: {xxx: 6}}
}
能够发现当规定项中存在数组(['b', /^b/, testFunc]
)时,匹配规定存在多种门路。而每种匹配门路,都有可能匹配多个门路的数据。因而定义两个树结构:
- 规定树
- 匹配数据树
转换流程如下图所示:
rules生成的数据结构命名为规定树
规定树和数据生成的数据结构命名为匹配数据树
通过对规定树和匹配数据树的操作能够很不便的实现各种配置,如匹配优先级options.priority
,能够通过批改规定树中各节点children的程序实现;options.matchFullRules
配置能够通过决定是否对匹配数据树进行裁剪实现。
发表回复