此篇文章是博主于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);
}
 
总结
- 如何实现深响应和浅响应
 - 如何实现只读和浅只读
 










