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。