Vue+Vuex应用CRUD操作中,如何追踪需更新的数据?
确定Vuex应用中需API Patch的变更内容方案
思路3:响应式快照对比(推荐)
不用绑定一堆变更事件,也能精准识别真正的脏数据:
- 从API拉取数据后,给每个实体对象加一个不可枚举的
_original属性,存一份该对象的深拷贝快照(嵌套实体也得拷贝) - 借助Vue双向绑定自动更新数据,用户编辑时不用额外处理标记
- 保存时遍历数据,对比每个对象和它的
_original快照:- 只有当实际值和快照不一样时,才标记为需要Patch的对象
- 新增的对象直接标记为
_isNew,保存时走Post接口 - 就算用户编辑后又改回原值,对比后会自动排除,不会误标
简单代码示例:
// 拉取数据时添加快照 async fetchData() { const res = await api.get('/entities'); this.entities = res.data.map(item => { const original = JSON.parse(JSON.stringify(item)); // 加不可枚举属性,不污染数据结构 Object.defineProperty(item, '_original', { value: original, enumerable: false, writable: false }); return item; }); } // 保存时对比生成请求 async saveChanges() { const patchTasks = []; const postTasks = []; this.entities.forEach(item => { if (item._isNew) { postTasks.push(api.post('/entities', item)); } else { // 对比当前值和快照,生成差异对象 const diff = getDiff(item, item._original); if (Object.keys(diff).length > 0) { patchTasks.push(api.patch(`/entities/${item.id}`, diff)); } } }); await Promise.all([...patchTasks, ...postTasks]); // 保存后更新快照,把当前值设为新的原始值 this.entities.forEach(item => { if (!item._isNew) { Object.defineProperty(item, '_original', { value: JSON.parse(JSON.stringify(item)), enumerable: false, writable: false }); } else { delete item._isNew; } }); } // 简易差异生成函数(也可以用工具库) function getDiff(current, original) { const diff = {}; for (const key in current) { if (typeof current[key] === 'object' && current[key] !== null) { const nestedDiff = getDiff(current[key], original[key]); if (Object.keys(nestedDiff).length > 0) { diff[key] = nestedDiff; } } else if (!Object.is(current[key], original[key])) { diff[key] = current[key]; } } return diff; }
思路2的工具选择(不用自己写深拷贝/对比)
如果一定要用深拷贝+全量对比,直接用现成工具:
- 深拷贝:用
lodash.cloneDeep,能处理嵌套实体、循环引用这些边缘情况 - 深对比:
lodash.isEqual可以精准对比对象所有层级的内容 - 生成差异:用
deep-object-diff库直接生成Patch需要的差异结构,不用自己写对比逻辑
示例代码:
import { cloneDeep, isEqual } from 'lodash'; import { diff } from 'deep-object-diff'; // 拉取数据时存两份:原始数据和可编辑数据 async fetchData() { const rawData = await api.get('/entities'); this.rawEntities = cloneDeep(rawData); this.entities = cloneDeep(rawData); } // 保存时处理变更 async saveChanges() { const patchTasks = []; const postTasks = []; this.entities.forEach(item => { if (!item.id) { // 假设新增数据没有id postTasks.push(api.post('/entities', item)); } else { const original = this.rawEntities.find(e => e.id === item.id); const changes = diff(original, item); if (Object.keys(changes).length > 0) { patchTasks.push(api.patch(`/entities/${item.id}`, changes)); } } }); await Promise.all([...patchTasks, ...postTasks]); // 更新原始数据 this.rawEntities = cloneDeep(this.entities); }
额外优化点
- 分页数据不用全量对比,只对比当前页和标记过修改的页,提升性能
- 嵌套实体如果是独立资源,可以单独处理其变更,比如只Patch嵌套实体的特定属性,减少请求数据量
- 弹窗新增数据时,给对象加
_isNew标记,方便区分Post/Patch操作
内容的提问来源于stack exchange,提问作者goodson
相关产品推荐
相关产品推荐

