如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~
作者:前端小王hs
阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主
此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来
书籍:《Vue.js设计与实现》 作者:霍春阳
本篇博文将在书第5.1节至5.4节的基础上进一步总结所提到的基础概念,附加了测试的代码运行示例,方便正在学习Vue3或想分析Vue3源码的朋友快速阅读
如有帮助,不胜荣幸
前文:
Vue3.js“非原始值”响应式实现基本原理笔记(一)
Vue3.js“非原始值”响应式实现基本原理笔记(二)
Vue3.js“非原始值”响应式实现基本原理笔记(三)
浅响应与深响应
假设传入一个嵌套着对象的对象
- 浅响应:只有表层对象具有响应式
- 深响应:递归地将对象的所有嵌套属性都转换为响应式的
在Vue3.js“非原始值”响应式实现基本原理笔记(三)中第一次出现了reactive
,其实就是对proxy
对象的一个封装函数,现在来看一下这样做出现的问题以及继续完善的方法
其实读到这里,可以发现书的走向就是不断的提出问题并完善响应式的一个过程,对于响应式的问题,就是不能触发副作用函数
我们先来看如何实现深响应
来看书中的代码:
const obj = reactive({ foo: { bar: 1 }
}); effect(() => { console.log(obj.foo.bar);
}); obj.foo.bar = 2; // 修改值不会触发响应
来看一下effect()
中的执行流程:
- 因为不是
lazy
,直接执行effectFn
,进而执行匿名函数 - 读取
obj.foo
,调用track
,obj.foo
和effectFn
进行关联 - 执行
return Reflect.get(target, key, receiver)
返回{bar:1}
所以整个过程bar
与effect
不会有关系,那么也就不会触发响应了
如果需要深响应,只需将返回的{bar:1}
再丢进reactive
里即可,这里就需要做一个判断,Reflect.get
返回的得是一个对象且不为null
,最终代码如下:
function reactive(obj) {return new Proxy(obj, {get(target, key, receiver) {if (key === 'raw') {return target;}track(target, key);const res = Reflect.get(target, key, receiver);if (typeof res === 'object' && res !== null) {return reactive(res);}return res;}// 省略其他拦截函数});
}
这样,深响应就实现了
但是有时候我们不需要所有的嵌套对象都实现响应,那么就催生了浅响应,也就是shallowReactive
,shallow是浅的意思
实现的过程也非常简单,只需添加一个形参,用于告知函数是否需要进行深响应,反之则浅响应,代码如下:
// 默认为 false,即非浅响应
function createReactive(obj, isShallow = false) {return new Proxy(obj, {get(target, key, receiver) {if (key === 'raw') {return target;}const res = Reflect.get(target, key, receiver);if (isShallow) {return res;}track(target, key); if (typeof res === 'object' && res !== null) { return reactive(res);}return res;}// 省略其他拦截函数});
}
最后,在createReactive
外套上reactive
和shallowReactive
,就是我们在文档中看到的API了:
function reactive(obj) {return createReactive(obj);
}function shallowReactive(obj) {return createReactive(obj, true);
}
只读和浅只读
只读的逻辑与浅响应相同,都是在新增形参在函数中进行判定,逻辑非常简单,代码如下:
// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {return new Proxy(obj, {// 拦截设置操作set(target, key, newVal, receiver) {// 如果是只读的,则打印警告信息并返回if (isReadonly) {console.warn(`属性 ${key} 是只读的`);return true;}// 省略其他return res;},deleteProperty(target, key) {// 如果是只读的,则打印警告信息并返回 if (isReadonly) { console.warn(`属性 ${key} 是只读的`); return true; } // 省略其他return res; } // 省略其他拦截函数 });
}
可以看到,如果是只读的,也就是isReadonly
为true
,那么不管是修改还是删除,都会弹出警告
同理,由于是只读的,所以只读的对象也没有必要触发effect
,代码如下:
get(target, key, receiver) {if (!isReadonly) {track(target, key)}
// 省略其他
}
readonly
如下所示:
function readonly(obj) { return createReactive(obj, false, true /* 只读 */);
}
当然,现在只是浅只读,如果是const obj = readonly({ foo: { bar: 1 } })
,在读取obj.foo.bar
时仍然可以修改
因为Reflect.get(target, key, receiver)
返回的对象又执行了reactive(res)
,所以还需进行一个判定,就是如果为只读,就进行再次调用readonly
,而不是调用reactive
,代码如下:
if (typeof res === 'object' && res !== null) { // 如果数据为只读,则调用 readonly 对值进行包装 return isReadonly ? readonly(res) : reactive(res);
}
最后,在createReactive
外套上readonly
和shallowReadonly
,就是我们在文档中看到的API了:
function readonly(obj) {return createReactive(obj, false, true);
}function shallowReadonly(obj) {return createReactive(obj, true, true);
}
总结
- 如何实现深响应和浅响应
- 如何实现只读和浅只读