持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情
4.1 响应式数据与副作用函数
4.1.1 副作用函数
副作用函数指的是会产生副作用的函数。 在前面TreeShake的那里有出现过。如下面代码所示:
function effect() {
document.body.innerHTML = 'hello vue3'
}
当 effect 函数执行的时候,它会设置body中的文本内容,但是 除了这个函数之外,任何函数都可以对body的文本内容进行读写。 也就是说, effect
函数的执行会间接或直接影响到其他函数的执行,就比如说别的函数把body的文本内容设为hello world,然后你运行 effect 函数把人家body的文本内容改成了hello vue3,这就是影响到其他函数了。
这种时候,我们就说 effect函数产生了副作用 。
副作用很容易产生,就比如说在函数内部修改外部的全局变量,这就已经产生了副作用了。
4.1.2 响应式数据
假设在一个副作用函数中,读取了某个对象的属性:
const obj = {text: 'hello vue3'}
// effect 函数的执行会读取 obj.text
function effect() {
document.body.innerText = obj.text
}
然后,重点来了, 我们希望 当 obj.text 的值发生变化时,副作用函数 effect 会重新执行。 如果能实现这个目标,那么我们就称这个obj对象是响应式数据。
4.2 响应式数据的基本实现
一个数据可以通过以下两条线索来实现响应式。
- 当副作用函数
effect执行的时候,会触发字段obj.text的 读取操作; - 当修改
obj.text的值的时候,会触发字段obj.text的 设置操作;
假设我们可以拦截一个对象的读取和设置操作,那么实现上面的需求就简单了。当我们读取字段 obj.text 的时候,我们可以把副作用函数存储到一个“桶”里,然后当我们设置 obj.text
的时候,我们再把副作用函数从这个“桶”里拿出来执行即可。
那么,如何才能拦截一个对象的读取和设置操作呢?在Vue2中,是通过 Object.defineProperty 函数来实现的,而在Vue3中, 则是通过代理对象 Proxy 来实现的。
上面的代码虽然实现了响应式,但是 还存在很多缺陷。 比如说,副作用函数逻辑和函数名是紧密耦合的,用人话说就是,我们直接通过名称 effect
来获取副作用函数的,改个名字就完蛋了,这种硬编码的方式很不灵活。我们要想办法去掉这种硬编码的机制,副作用函数的名字可以任意取,甚至是一个匿名函数。
4.3 设计一个完善的响应式系统
从上面我们可以看出 一个响应式系统的工作流程:
- 当 读取 操作发生时,将副作用函数收集到“桶”中;
- 当 设置 操作发生时,从“桶”中取出副作用函数并执行;
上面实现的微型响应式的缺陷就是它的硬编码机制,一旦副作用函数不叫 effect,那段代码就无法正确的工作了。 而一个完善的响应式系统,应该做到 哪怕副作用函数是一个匿名函数,也应该能被正确收集到“桶”里去。
我们可以通过 提供一个用来注册副作用函数的机制 来实现我们的需求。