reactive是如何实现深层响应的?
2021-04-17 12:13 金色海洋(jyk) 阅读(314) 评论(0) 编辑 收藏 举报深层响应的 reactive
看过官网文档的都知道,Vue3 的响应性分为浅层和深层,我们常用的 reactive 是深层的。
我们也都知道,reactive 是使用 proxy 来实现响应性的,那么问题来了:
既然 proxy 的拦截操作是浅层的,对于嵌套属性的操作无感,那么 reactive 是如何实现深层响应的呢?
这个就得看看 源码了。
// reactivity.js
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
if (key === "__v_isReactive" /* IS_REACTIVE */) {
return !isReadonly;
}
else if (key === "__v_isReadonly" /* IS_READONLY */) {
return isReadonly;
}
else if (key === "__v_raw" /* RAW */ &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap).get(target)) {
return target;
}
const targetIsArray = isArray(target);
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
const res = Reflect.get(target, key, receiver);
if (isSymbol(key)
? builtInSymbols.has(key)
: isNonTrackableKeys(key)) {
return res;
}
if (!isReadonly) {
track(target, "get" /* GET */, key);
}
if (shallow) {
return res;
}
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
return shouldUnwrap ? res.value : res;
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res); // 重点在这里。。。
}
return res;
};
}
这是拦截 get 操作的代码。
上面的可以跳过,直接看倒数第二个 return。
简单地说,各种判断后,返回一个新的 reactive。
就是说,给子子属性赋值的时候,需要先获取第一级的对象,然后把这个对象变成 reactive 的形式返回,这样就可以实现层层属性的拦截了。
监听任意属性的值的变化。
最简单的方式就是用 watch 的深度监听功能。
watch (() => reactive1, () => {
// 属性值变了。
}, {deep:true})
这样任意一层的属性的变化,都可以获知,只是有个小问题,只知道有属性值变了,但是不知道具体是哪个属性变了。两个参数也都是新值,没有旧值了。
那么如果一定要知道是哪个属性变了呢?
用 proxy 套个娃
既然 Proxy 里面可以进行各种拦截,那么为啥不顺便返回来改了哪个属性呢?
不管那么多了,自己给 reactive 套个 proxy 再次拦截试一试。
const myProxy = (_target, callback, arr) => {
const _arr = arr || []
const proxy = new Proxy(_target, {
get: function (target, key, receiver) {
switch (key) {
case '__v_isRef':
case 'toJSON':
case 'symbol':
case 'Symbol(Symbol.toStringTag)':
break;
default:
// 判断是不是对象
if (typeof target[key] === 'object') {
// console.log(`获取 对象 ${key}!`, target[key])
_arr.push(key)
// 源头监听
if (typeof callback === 'function') {
callback('get', key, target[key], _arr)
}
} else if (typeof key !== 'symbol') {
// console.log('获取 属性 ', key, target[key])
}
break;
}
// 调用原型方法
const res = Reflect.get(target, key, target)
if (typeof res === 'object') {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return myProxy(res, callback, _arr) // 递归
}
return res
},
set: function (target, key, value, receiver) {
if (key !== '__watch') {
// 源头监听
if (typeof callback === 'function') {
callback('set', key, value, _arr)
}
// console.log('路径:', _arr.join('-'))
_arr.length = 0
// console.log(`设置 ${key}:${value}!`)
}
// 调用原型方法
return Reflect.set(target, key, value, target)
}
})
// 返回实例
return proxy
}
使用方式
const ret3 = myProxy({
a:'11',
b: {
b1:'',
b2: {
b21: {
b211: '111'
}
},
b3: {
b31: {
b311: '2222'
}
}
}
}, (kind, key, value, path) => {
console.log(`ret3 - 定义端监听:【${kind}】 ${key}-`, value, path)
})
const retChage = () => {
ret3.b.b2.b21.b211 = 'eeee'
}
-
callback
古老的回调函数,把属性名称和属性值返回来就好。 -
_arr
因为嵌套属性可能是很多级别的,而 set 只能获知最后一个属性的名称,中间的过程全在 get 里面。
于是就想做个数组把每一级的属性名称存进去。
修改属性的时候也确实是一级一级的存进去了,但是直到我把 ret3 放到了模板里面……
模板里面也是要获取值的,也会触发 get 事件,也会往数组里面 push 属性名称。
于是问题来了,如何区分是模板触发的 get 还是给属性赋值触发的 get?
到目前为止还是没有想到办法。
这样的话,就只有最后一个属性是准确的,前面的就不一定了。
折腾半天,只是知道了一些原理,但是最初的问题还是没有解决。

层次越深,对象结构越复杂,模板里用的越多,这个数据就越长,所以基本没啥用了。
只拿到最后一个属性,没有中间过程的话,对于简单的,或者特定的还是可以用用的,但是想通用就基本没戏了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!