如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~
作者:前端小王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);
}
总结
- 如何实现深响应和浅响应
- 如何实现只读和浅只读