published: true
date: 2022-2-3
tags: ‘前端框架 Vue’
Vue3 Reactivity
本章介绍Vue中另一个非常重要的模块,响应式。介绍了基本原理(含图)简单实现以及如何阅读源码。
致谢Vue Mastery非常好的课程,可以转载,但请声明源链接:文章源链接justin3go.com(有些latex公式某些平台不能渲染可查看这个网站)
Reactivity
作用
- 学习Vue3的响应式原理;
- 提高调试能力;
- 使用响应式这个模块进行一些骚操作;
- 为Vue做贡献
Vue是如何知道更新这些模板的呢?

当price发生改变,Vue知道怎么更新这些模板。

JavaScript并不会更新!
自己实现一个响应式原理
基本思路:当价格或者数量更新时,让它再跑一次;

// effct这个函数就是让total重新计算一次;
let effect = () => {
total = price * quantity} // 缩短上述中间代码
let dep = new Set() // 去储藏effect,保证不会添加重复值

- 其中**track()**函数是添加这个数据;
- 而**trigger()**函数<触发函数>会遍历我们存的每一个effect,然后运行它们;

如何存储,让每个属性拥有自己的依赖
通常,我们的对象会有多个属性,每个属性都需要自己的dep(依赖关系),或者说effect的Set;


- dep就是一个effect集(Set),这个effect集应该在值发生改变时重新运行;

-
要把这些dep储存起来,且方便我们以后再找到它们,我们需要创建有一个depsMap,它是一张存储了每个属性其dep对象的图;
-
使用对象的属性名作为键,比如数量和价格,值就是一个dep(effects集)
-
代码实现:
const depsMap = new Map()
function track(key) {
// key值就是刚才的价格或者数量
let dep = depsMap.get(key); // 找到属性的依赖
if (!dep){
//如果没有,就创建一个
depsMap.set(key, (dep = new Set()))
}
dep.add(effect) // 添加effect,注意dep(Set)会去重
}
function trigger(key) {
let dep = depsMap.get(key) // 找到键值的依赖
if (dep) {
// 如果存在,遍历并运行每个effect
dep.forEach(effect => {
effect()
})
}
}
// main
let product = {
price: 5, quantity: 2};
let total = 0;
let effect = () => {
total = product.price * product.quantity;
}
// 存储effect
track('quantity')
effect()
- 结果:

如果我们有多个响应式对象呢?

之前的是这样:

这里depsMap是对每个属性进行存储,再上一层,我们需要对每个对象进行存储,其中每个对象包括了不同的属性,所以我们在这之上有用了一张表(Map),来存储每个对象;

Vue3中这个图叫做targetMap,需要注意的是这个数据结构是WeakMap<就是键值为空时会被垃圾回收机制清除,就是响应式对象消失后,这里面存的相关依赖也会被自动清除>
const targetMap = new WeakMap(); // 为每个响应式对象存储依赖关系
// 然后track()函数就需要首先拿到targetMap的depsMap
function track(target, key) {
let depsMap = targetMap.get(target); // target是响应式对象的名称,key是对象中属性的名称
if (!depsMap){
// 不存在则为这个对象创建一个新的deps图
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) // 检查属性是否有依赖的关系
// 如果有的话将遍历dep,运行每个effect
if (dep){
dep.forEach(effect => {
effect()})
}
}
// main
let product = {
price: 5, quantity: 2}
let total = 0
let effect = () => {
total = product.price * product.quantity
}
track(product, "quantity")
effect()
运行:

总结
响应式就是在每次更新值的时候重新计算一次,但是由于某些依赖什么什么的,某些对象对应的属性会导致另外的变量变化,所以需要一些数据结构去记录这些变化,就形成了下面的这些表(Map, Set)

但目前我们还没有办法让我们的effect自动重新运行,这将在后续讲到;
代理与反射
引入
上一部分我们使用track()与trigger()显式构造了响应式引擎,这部分我们希望其自动跟踪和触发;
需求:
- 访问了产品的属性或者说使用了get方法,就是我们想要调用track去保存effect的时候
- 产品的属性改变或者说使用了set方法,就是我们想要调用trigger来运行那些保存了的effect
解决:
- Vue2中,使用的是ES5中的Object.defineProperty()去拦截get或set;
- Vue3中,使用的是ES6中的代理和反射去实现相同的效果;
代理与反射基础
通常我们会用三种方法获取某个对象中的属性:
-
let product = { price: 5, quantity: 2}; product.quantity
-
product[quantity]
-
Reflect.get(product, ‘quantity’