1 | <div id="app"> |
在上面的例子中,我们点击按钮,页面上的 price 和 totalPrice 都发生了改变,当我们改变了 price 的值时,Vue 是如何知道需要更新 price,并且依赖 price 的 totalPrice 也同样被更新。
这就是 Vue 的响应式系统发挥的作用。
下面我们将自己实现一个响应式系统,从而了解 Vue 是如何实现这一切的。
触发变量更新
1 | let price = 5 |
在这个例子中的问题是 price 改变之后, tenProductsPrice 的输出依然会是50,并不会随 price 的改变而改变。
我们首先来解决这个问题,要想让 tenProductsPrice 和 priceWithDiscount 改变,我们需要 total 的获取是一个可重复的过程
1 | let tenProductsPrice = 0 |
所以我们将 tenProductsPrice 和 priceWithDiscount 的计算过程封装成一个函数,以便之后再去运行它。
接下来的问题是,我们何时去运行它?
首先是第一次求值的时候,直接先运行一次
1 | let price = 5 |
下次再调用 effect 的时候,就是在 price 改变的时候。
目前我们有两个变量依赖于 price,改变的时候需要调用两个 effect 函数,之后有可能会有更多依赖 price 的变量,也就会有更多的 effect 函数。
所以我们需要存储 effect 函数,以便之后调用方便
1 | // 用于存储effect,使用set可以防止添加同样的 effect 多次执行 |
这样我们就把 effect 存储到了一个集合中,在 price 改变的时候,调用 trigger 就可以触发所有依赖这个变量的值更新了。
多个变量的依赖
上面的例子我们只有一个 price 变量在更新时需要触发其他值的更新,那如果有多个值需要去触发更新呢?看下面的例子
1 | const product = { price: 5, quantity: 2 } |
现在我们有这么一个对象,我们需要依赖对象里的属性值,并在其改变的时候触发更新。
根据之前的经验来看,对于每个属性来说,都需要一个 dep 来存储 effect,以便在之后属性值变化的时候执行 trigger,触发更新
所以我们使用一个 Map 来保存每个变量对应的 dep,也就是需要被执行的 effect 函数
1 | const depsMap = new Map() |
接下来我们测试一下
1 | const product = { price: 5, quantity: 2 } |
total 在 quantity 变化之后也被更新了。
多个响应式对象
如果我们出了 product 对象,还有更多的对象可能会被依赖呢?
例如 let user = { name: 'xx', age: 22 }
我们的目标还是要存储 effect 到对应的 deps 集合中去
在上面的例子中 product 有一个 depsMap 来存储了 key 和 deps 的对应关系
对应上面的思路,我们也需要去存储每个对象和其 depsMap 的对应关系,我们可以叫它 targetMap
1 | const targetMap = new WeakMap() |
总结
上面例子中所使用的变量/函数名称(targetMap、depsMap、track、effect、trigger),都是和 Vue3 源码中一样的,这就是 Vue3 响应式的一部分逻辑。
基于目前的实现,我们实现了一个简易的“响应式”,只不过需要手动 trigger。
想要自动触发,还需要一个很重要的 API —— Proxy。
下篇我们来继续完善代码,让更新不再需要手动 trigger。