Vue状态嵌套对象变更路径获取及脏标记设置问题咨询
问题解答
原生配置的局限性
你当前使用的Vue内置deep: true监听器无法直接实现需求。因为深度监听器只会感知到source对象发生了变更,既不会返回具体的变更字段路径,也因为引用类型的特性,传入handler的oldValue和newValue指向同一个内存地址,无法直接对比得到差异点。
可行实现方案
方案1:Vue3环境下使用Proxy代理(推荐)
通过自定义Proxy拦截所有属性赋值操作,可以直接拿到变更路径,同时给目标对象加脏标记,性能开销最低,示例代码如下:
function createDirtyProxy(target, path = '') { if (typeof target !== 'object' || target === null) return target return new Proxy(target, { set(obj, prop, value) { // 拼接变更路径 const currentPath = path ? Array.isArray(obj) ? `${path}[${prop}]` : `${path}.${prop}` : String(prop) // 给当前修改的对象加脏标记 obj._dirty = true // 嵌套属性继续用Proxy包装 obj[prop] = createDirtyProxy(value, currentPath) // 此处可拿到currentPath做其他业务处理 console.log('变更路径:', currentPath) return true } }) } // 初始化时给原始数据套一层Proxy created() { const rawSource = {} // 替换为你的原始嵌套对象 this.source = createDirtyProxy(rawSource) }
方案2:Vue2环境下使用深度差异对比
Vue2没有原生Proxy支持,可以通过深度对比新旧对象的差异计算出变更路径,再给对应对象加标记,示例代码如下:
// 可直接使用成熟的差异对比库,也可自行实现深度对比逻辑 import { diff } from 'deep-diff' export default { data() { return { source: {}, _oldSource: {} // 用于缓存上一次的对象快照 } }, watch: { source: { deep: true, handler(newVal) { const differences = diff(this._oldSource, newVal) differences.forEach(diffItem => { // 拼接符合要求的变更路径 const path = diffItem.path.reduce((acc, cur, idx) => { return typeof cur === 'number' ? `${acc}[${cur}]` : idx === 0 ? cur : `${acc}.${cur}` }, '') // 定位到变更的目标对象 let target = newVal for (let i = 0; i < diffItem.path.length - 1; i++) { target = target[diffItem.path[i]] } // 用$set新增属性保证响应式 this.$set(target, '_dirty', true) console.log('变更路径:', path) }) // 更新快照缓存 this._oldSource = JSON.parse(JSON.stringify(newVal)) } } }, created() { // 初始化快照缓存 this._oldSource = JSON.parse(JSON.stringify(this.source)) } }
注意事项
- 深度差异对比方案的性能开销随对象嵌套层级提升而增大,如果你的对象结构极深、修改操作频繁,优先考虑升级到Vue3使用Proxy方案。
- 如果不需要保留
_dirty的响应性,也可以直接赋值,不需要调用$set。
内容的提问来源于stack exchange,提问作者T. Cem Yılmaz
相关产品推荐
相关产品推荐

