社区精选|手写一个业务数据比对库
今天小编为大家带来的是社区作者 jump__jump 的文章,让我们一起来学习今天的新知识。
// newVal 表示新数据,oldVal 表示老数据
const simpleObjDiff = ({
newVal,
oldVal,
}): Record<string, any> => {
// 当前比对的结果
const diffResult: Record<string, any> = {};
// 已经检查过的数据项,可以优化遍历性能
const checkedKeys: Set<string> = new Set();
// 遍历最新的对象属性
Object.keys(newVal).forEach((key: string) => {
// 将新数据的 key 记录一下
checkedKeys.add(key);
// 如果当前新的数据不等于老数据,直接把新的比对结果放入
if (newVal[key] !== oldVal[key]) {
diffResult[key] = newVal[key];
}
});
// 遍历之前的对象属性
Object.keys(oldVal).forEach((key) => {
// 如果已经检查过了,不在进行处理
if (checkedKeys.has(key)) {
return;
}
// 新的数据有,但是老数据没有可以认为数据已经不存在了
diffResult[key] = null;
});
return diffResult;
};
const result = simpleObjDiff({
newVal: {
a: 1,
b: 1,
},
oldVal: {
a: 2,
c: 2,
},
});
// => 返回结果为
result = {
a: 1,
b: 1,
c: null,
};
JSON.stringify("123");
// '"123"'
JSON.stringify(123);
// '123'
JSON.stringify(new Date());
// '"2022-11-29T15:16:46.325Z"'
JSON.stringify([1, 2, 3]);
// '[1,2,3]'
JSON.stringify({ a: 1, b: 2 });
// '{"b":2,"a":1}'
JSON.stringify({ b: 2, a: 1 });
// '{"b":2,"a":1}'
JSON.stringify({ b: 2, a: 1 }, ["a", "b"]);
// '{"a":1,"b":2}'
JSON.stringify({ b: 2, a: 1 }, ["a", "b"]) === JSON.stringify({ a: 1, b: 2 });
// true
const simpleObjDiff = ({
newVal,
oldVal,
}): Record<string, any> => {
// ... 之前的代码
// 遍历最新的对象数据
Object.keys(newVal).forEach((key: string) => {
// 当前已经处理过的对象 key 记录一下
checkedKeys.add(key);
// 先去查看类型,判断相同类型后再使用 JSON.stringify 获取字符串结果进行比对
if (
typeof newVal[key] !== typeof oldVal[key] ||
JSON.stringify(newVal[key]) !== JSON.stringify(oldVal[key])
) {
diffResult[key] = newVal[key];
}
});
// ... 之前的代码
};
const result = simpleObjDiff({
newVal: {
a: 1,
b: 1,
d: [1, 2, 3],
},
oldVal: {
a: 2,
c: 2,
d: [1, 2, 3],
},
});
// => 返回结果为
result = {
a: 1,
b: 1,
c: null,
};
const simpleObjDiff = ({
newVal,
oldVal,
options,
}): Record<string, any> => {
// ... 之前的代码
// 获取用户定义的 diff 函数
const { diffFun } = { ...DEFAULT_OPTIONS, ...options };
// 判断当前传入数据是否是函数
const hasDiffFun = typeof diffFun === "function";
// 遍历最新的对象数据
Object.keys(newVal).forEach((key: string) => {
// 当前已经处理过的对象 key 记录一下
checkedKeys.add(key);
let isChanged = false;
if (hasDiffFun) {
// 把当前属性 key 和对应的新旧值传入从而获取结果
const diffResultByKey = diffFun({
key,
newPropVal: newVal[key],
oldPropVal: oldVal[key],
});
// 返回了结果则写入 diffResult,没有结果认为传入的函数不处理
// 注意是不处理,而不是认为不变化
// 如果没返回就会继续走 JSON.stringify
if (
diffResultByKey !== null &&
diffResultByKey !== undefined
) {
diffResult[key] = diffResultByKey;
isChanged = true;
}
}
if (isChanged) {
return;
}
if (
typeof newVal[key] !== typeof oldVal[key] ||
JSON.stringify(newVal[key]) !== JSON.stringify(oldVal[key])
) {
diffResult[key] = newVal[key];
}
});
// ... 之前的代码
};
const result = simpleObjDiff({
newVal: {
a: [12, 3, 4],
b: 11,
},
oldVal: {
a: [1, 2, 3],
c: 22,
},
options: {
diffFun: ({
key,
newPropVal,
oldPropVal,
}) => {
switch (key) {
// 处理对象中的属性 a
case "a":
// 当前数组新旧数据都有的数据项才会保留下来
return newPropVal.filter((item: any) => oldPropVal.includes(item));
}
// 其他我们选择不处理,使用默认的 JSON.stringify
return null;
},
},
});
// => 结果如下所示
result = {
a: [3],
b: 11,
c: null,
};
import fastJson from "fast-json-stringify";
const stringify = fastJson({
title: "User Schema",
type: "object",
properties: {
firstName: {
type: "string",
},
lastName: {
type: "string",
},
age: {
description: "Age in years",
type: "integer",
},
},
});
stringify({
firstName: "Matteo",
lastName: "Collina",
age: 32,
});
// "{\"firstName\":\"Matteo\",\"lastName\":\"Collina\",\"age\":32}"
stringify({
lastName: "Collina",
age: 32,
firstName: "Matteo",
});
// "{\"firstName\":\"Matteo\",\"lastName\":\"Collina\",\"age\":32}"
// 添加异常错误抛出
const invariant = (condition: boolean, errorMsg: string) => {
if (condition) {
throw new Error(errorMsg);
}
};
// 判断是否是真实的对象
const isRealObject = (val: any): val is Record<string, any> => {
return Object.prototype.toString.call(val) === "[object Object]";
};
simpleObjDiff = ({
newVal,
oldVal,
options,
}: SimpleObjDiffParams): Record<string, any> => {
// 添加错误传参处理
invariant(!isRealObject(newVal), "params newVal must be a Object");
invariant(!isRealObject(oldVal), "params oldVal must be a Object");
// ...
const { diffFun, empty } = { ...DEFAULT_OPTIONS, ...options };
// ...
Object.keys(oldVal).forEach((key) => {
// 如果已经检查过了,直接返回
if (checkedKeys.has(key)) {
return;
}
// 设定空数据,建议使用 null 或 空字符串
diffResult[key] = empty;
});
};
const simpleListDiff = ({
newVal,
oldVal,
options,
}: SimpleObjDiffParams) => {
const opts = { ...DEFAULT_OPTIONS, ...options };
// 获取当前的主键 key 数值,不传递 key 默认为 'id'
const { key, getChangedItem } = opts;
// 增删改的数据
const addLines = [];
const deletedLines = [];
const modifiedLines = [];
// 添加检测过的数组主键,ListKey 是数字或者字符串类型
const checkedKeys: Set<ListKey> = new Set<ListKey>();
// 开始进行传入数组遍历
newVal.forEach((newLine) => {
// 根据主键去寻找之前的数据,也有可能新数据没有 key,这时候也是找不到的
let oldLine: any = oldVal.find((x) => x[key] === newLine[key]);
// 发现之前没有,走添加数据逻辑
if (!oldLine) {
addLines.push(newLine);
} else {
// 更新的数据 id 添加到 checkedKeys 里面去,方便删除
checkedKeys.add(oldLine[key]);
// 传入函数 getChangedItem 来获取结果
const result = getChangedItem!({
newLine,
oldLine,
});
// 没有结果则认为当前数据没有改过,无需处理
// 注意,和上面不同,这里返回 null 则认为数据没有修改
if (result !== null && result !== undefined) {
modifiedLines.push(result);
}
}
});
oldVal.forEach((oldLine) => {
// 之前更新过不用处理
if (checkedKeys.has(oldLine[key])) {
return;
}
// 剩下的都是删除的数据
deletedLines.push({
[key]: oldLine[key],
});
});
return {
addLines,
deletedLines,
modifiedLines,
};
};
const result = simpleListDiff({
newVal: [{
id: 1,
cc: "bbc",
},{
bb: "123",
}],
oldVal: [{
id: 1,
cc: "bb",
}, {
id: 2,
cc: "bdf",
}],
options: {
// 传入函数
getChangedItem: ({
newLine,
oldLine,
}) => {
// 利用对象比对 simpleObjDiff 来处理
const result = simpleObjDiff({
newVal: newLine,
oldVal: oldLine,
});
// 发现没有改动,返回 null
if (!Object.keys(result).length) {
return null;
}
// 否则返回对象比对过的数据
return { id: newLine.id, ...result };
},
key: "id",
},
});
// => 返回结果为
result = {
addedLines: [{
bb: "123",
}],
deletedLines: [{
id: 2,
}],
modifiedLines: [{
id: 1,
cc: "bbc",
}],
};
const simpleListDiff = ({
newVal,
oldVal,
options,
}: SimpleObjDiffParams) => {
const opts = { ...DEFAULT_OPTIONS, ...options };
// 获取当前的主键 key 数值,不传递 key 默认为 'id'
const { key } = opts;
let { getChangedItem } = opts;
// 如果没有传递 getChangedItem,就使用 simpleObjDiff 处理
if (!getChangedItem) {
getChangedItem = ({
newLine,
oldLine,
}) => {
const result = simpleObjDiff({
newVal: newLine,
oldVal: oldLine,
});
if (!Object.keys(result).length) {
return null;
}
return { [key]: newLine[key], ...result };
};
}
//... 之前的代码
};
const simpleListDiff = ({
newVal,
oldVal,
options,
}: SimpleObjDiffParams) => {
const opts = { ...DEFAULT_OPTIONS, ...options };
// 此时传入 sortName,不传递则不考虑排序问题
const { key, sortName = "" } = opts;
// 判定是否有 sortName 这个配置项
const hasSortName: boolean = typeof sortName === "string" &&
sortName.length > 0;
let { getChangedItem } = opts;
if (!getChangedItem) {
//
}
const addLines = [];
const deletedLines = [];
const modifiedLines = [];
// 添加 noChangeLines
const noChangeLines = [];
const checkedKeys: Set<ListKey> = new Set<ListKey>();
newVal.forEach((newLine, index: number) => {
// 这时候需要查询老数组的索引,是利用 findIndex 而不是 find
let oldLineIndex: any = oldVal.findIndex((x) => x[key] === newLine[key]);
// 没查到
if (oldLineIndex === -1) {
addLines.push({
...newLine,
// 如果有 sortName 这个参数,我们就添加当前序号(索引 + 1)
...hasSortName && { [sortName]: index + 1 },
});
} else {
// 通过索引来获取之前的数据
const oldLine = oldVal[oldLineIndex];
// 判定是否需要添加顺序参数,如果之前的索引和现在的不同就认为是改变的
const addSortParams = hasSortName && index !== oldLineIndex;
checkedKeys.add(oldLine[key]);
const result = getChangedItem!({
newLine,
oldLine,
});
if (result !== null && result !== undefined) {
modifiedLines.push({
...result,
// 更新的数据同时添加排序信息
...addSortParams && { [sortName]: index + 1 },
});
} else {
// 这里是没有修改的数据
// 处理数据没改变但是顺序改变的情况
if (addSortParams) {
noChangeLines.push({
[key!]: newLine[key!],
[sortName]: index + 1,
});
}
}
}
});
//... 其他代码省略,删除不用考虑顺序了
return {
addLines,
deletedLines,
modifiedLines,
// 返回不修改的 line
...hasSortName && {
noChangeLines,
},
};
};
simpleListDiff({
newVal: [
{ cc: "bbc" },
{ id: 1, cc: "bb" }
],
oldVal: [
{ id: 1, cc: "bb" }
],
options: {
key: "id",
sortName: "sortIndex",
},
});
// 同样也支持为新增和修改的数据添加 sortIndex
result = {
addedLines: [
{
cc: "bbc",
// 新增的数据目前序号为 1
sortIndex: 1,
},
],
// id 为 1 的数据位置变成了 2,但是没有发生数据的改变
noChangeLines: [{
id: 1,
sortIndex: 2,
}],
deletedLines: [],
modifiedLines: [],
};
import { commonDiff } from "diff-helper";
commonDiff({
a: {
b: 2,
c: 2,
d: [1, 3, 4, [3333]],
},
}, {
a: {
a: 1,
b: 1,
d: [1, 2, 3, [223]],
},
});
// 当前结果均是对象,不过当前会增加 type 帮助识别类型
result = {
type: "obj",
a: {
type: "obj",
a: null,
b: 1,
c: 2,
d: {
type: "arr",
// 数组第 2 个数据变成了 3,第 3 数据变成了 4,以此类推
1: 3,
2: 4,
3: {
type: "arr",
0: 223,
},
},
},
};
// 更新表单项数据,为了性能,不建议每次都传递一整个 user
this.setData({ [`user.${name}`]: value });
// 设置数组里面某一项数据
this.setData({ [`users[${index}].${name}`]: value });
const result = diff({
a: 1,
b: 2,
c: "str",
d: { e: [2, { a: 4 }, 5] },
f: true,
h: [1],
g: { a: [1, 2], j: 111 },
}, {
a: [],
b: "aa",
c: 3,
d: { e: [3, { a: 3 }] },
f: false,
h: [1, 2],
g: { a: [1, 1, 1], i: "delete" },
k: "del",
});
// 结果
{
"a": 1,
"b": 2,
"c": "str",
"d.e[0]": 2,
"d.e[1].a": 4,
"d.e[2]": 5,
"f": true,
"h": [1],
"g.a": [1, 2],
"g.j": 111,
"g.i": null,
"k": null
}
关注公众号:拾黑(shiheibook)了解更多
赞助链接:
关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
随时掌握互联网精彩
- 国产服务器操作系统最新报告,统信UOS持续领跑!
- 澳元兑换人民币汇率2023年8月20日
- 卢布汇率人民币2023年7月7日
- 千亿投资打水漂 盘点那些倒下和即将倒下的新能源品牌
- 5月安卓新机流畅榜发布,榜首你可能没猜到!
- 统信UOS荣获“年度最佳信创产品奖”
- 最新骗局!比杀猪盘更可怕的是"杀鸟盘"
- 来 OpenInfra Live: Keynotes 与 OpenStack 和 K8S 等全球社区领袖互动
- 美国半导体产业发展现状及未来政策趋势
- 高通将模仿 Switch 开发 Android 游戏机;百度Apollo将与禾赛联合研发激光雷达|Do早报
- 你们都在抢的微信红包封面来啦
- 金立2000多万台手机被植入“拉活木马”,相关公司非法牟利近3000万