“这是我参与更文挑战的第4天,活动详情查看: 更文挑战”
上文《Vue3 响应式原理探索Part 1 - 20行代码实现响应式》,我们介绍了什么是响应式,以及一个弱鸡版本的实现。并提出了一个疑问,我们这篇来解决它。
多个属性如何创建独立的响应式
let param = { width: 4, height: 5 };
let size = 0;
let effect = () => {
size = param.width * param.height
}
...
我们的 param 有两个属性 width | height ,他们都分别被其他变量依赖着,譬如:
let param = { width: 4, height: 5 };
let widthNew = param.width * 2;
let sizeNew = Math.PI * param.width * param.width;
let heightNew = param.height * 3;
可以看到 width 分别被 widthNew sizeNew 依赖,height 被 heightNew 依赖。当 width 或 height 更新时,他们相应的依赖需要自动更新。但我们如何做到呢。
Map 是ES6引进的一个类型,专门用来存储键值型的数据,可以通过 key 设置(set) 或 获取(get) 相应的 value。我们可以考虑将 width 和 height 存储做为 key, 将他们相应的依赖作为值存储进 value。
const depsMap = new Map();
function track(key) {
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set));
}
dep.add(effect);
}
function trigger(key) {
let dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => {
effect();
});
}
}
let param = { width: 5, height: 2 }
let size = 0;
let effect = () => {
size = param.width * param.height;
};
track('height');
track('width');
effect();
console.log(size); // => 10
param.height = 3;
trigger('height');
console.log(size); // => 15
param.width = 6;
console.log(size); // => 15,没有手动触发 'width' 的依赖,所以还是15
trigger('width');
console.log(size); // => 18
以上的实现可以看出,我们只要在相应的属性变化时,手动执行相应的 trigger 即可实现简单的响应式。
多个响应式对象
如果有多个不同的param对象需要创建响应式,存储结构又该如何调整呢。毕竟现实的工程项目中,不可能仅仅只有一个对象。
我们可以考虑再做一层数据结构的封装,将上述实现的每个对象的依赖 depsMap 都做为 value,但问题来了,用什么作为 key 呢。
还好 ES6 提供了另外一个结构, WeakMap。
WeakMap 能够将对象做为自己的 key ,而且它只能将对象做为 key。 Amazing 。
let product = { width: 5, height: 2 }
const targetMap = new WeakMap()
targetMap.set(product, "example code to test")
console.log(targetMap.get(product)) // ---> "example code to test"
我们将 WeakMap 的实例命名为 targetMap, 调整后的存储结构如下图,图中的 product quantity price total 替换为我们示例中的 param width height size 即可理解。
当我们创建依赖或者触发的时候,我们需要将相应的对象作为参数传入,这样才知道应该去响应这个对象。具体实现如下:
const targetMap = new WeakMap();
function track(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(effect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
let dep = depsMap.get(key);
if (dep) {
dep.forEach(element => {
element();
});
}
}
let param = { width: 5, height: 2 };
let size = 0;
let effect = () => {
size = param.width * param.height;
}
track(param, 'height');
track(param, 'width');
effect();
console.log(size); // 10;
param.height = 3;
trigger(param, 'height');
console.log(size); // 15
param.width = 6;
trigger(param, 'width');
console.log(size); // 18
通过上述实现即可有效的追踪多个对象的依赖,在创建一个响应式系统时,它是一个重要的前菜。
新的疑问
但我们如何避免弱鸡的手动的 trigger 函数,而自动触发呢。另外,我们能不手动的 track 吗。
答案是可以的。
我们可以先学习
- Proxy,一个Javascript 代理 Proxy 的成长之旅
- Reflect,了解学习 Proxy 的好朋友 - Reflect
然后就可以基于 Proxy + Reflect,用 Vue3 响应式原理探索- Part 3 Proxy + Reflect + activeEffect 实现自动响应式了。