Vue3设计与实现共读-响应系统(二)

517 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

设计一个完善的响应系统

上一节中,我们已经实现了一个微型响应系统。这次我们将尝试构造一个更加完善的响应系统。

实现一个响应系统需要以下的工作流程:

  • 当读取操作发生时,将副作用函数收集到桶中;
  • 当设置操作发生时,从桶中取出副作用函数并执行

上一节中我们硬编码了一个effect函数,并将其收集到桶中,而这并不符合响应系统的要求,我们要实现的是任何函数,包括匿名函数都能被收集到。

// 用一个全局变量存储被注册的副作用函数
let activeEffect
// effect 函数用于注册副作用函数
function effect(fn) {
  // 当调用effect 注册副作用函数时,将副作用函数fn赋值给activeEffect
  activeEffect = fn
  // 执行副作用函数
  fn()
}

首先我们定义一个全局变量activeEffect,初始值是undefined,它的作用是存储被注册的副作用函数。接下来我们就需要改变effect函数,使之成为一个用来注册副作用函数的函数,effect函数接受一个参数fn,即要注册的副作用函数。

effect(
  // 一个匿名的副作用函数
  () => {
    document.body.innerText = obj.text
  }
)

这里,我们使用了一个匿名函数作为effect函数的参数。当effect执行的时候,首先把匿名副作用函数fn赋值给全局变量activeEffect。接着执行被注册的匿名副作用函数,这时就会触发响应式数据的obj.text的读取操作,进而触发代理对象Proxy的get拦截:

const obj = new Proxy(data, {
  get(target, key) {
    // 将activeEffect中存储的副作用函数收集到桶中
    if (activeEffect) { // 新增
      bucket.add(activeEffect) // 新增
    } // 新增
    return target[key]
  },
  set(target, key, newVal) {
    target[key] = newVal
    bucket.forEach(fn => fn())
    return true
  }
})

上述代码中,副作用已经存储到了activeEffect中,所以在get拦截的时候,函数内应该把activeEffect收集到桶中,这样响应式便不再依赖副作用函数的名字了。

但是继续进行时,我们在响应式数据obj中添加一个不存在的属性时:

effect(
  // 匿名副作用函数
  () => {
    console.log('effect run'); // 会打印两次
    document.body.innerText = obj.text
  }
)

setTimeout(() => {
  // 副作用函数中并没有读取 notExist 属性的值
  obj.notExist = 'hello vue3'
}, 1000);

匿名副作用函数内部读取了字段obj.text值,此时匿名副作用函数与字段obj.text之间建立响应联系。接着,我们开启一个定时器,在一秒钟之后为对象obj,添加新的属性。理论上,在副作用函数内部没有读取到obj.notExist属性的值,因此字段obj.notExist不应该与匿名副作用函数建立响应式联系,定时器内部的语句不应该触发副作用函数的执行。但是实际上我们操作会发现,匿名副作用函数确实是执行了,这显然不是我们响想要的。因此我们必须重新设计一个桶的数据结构去实现需求的功能。

那么如何去实现这一功能呢,我们将在下一篇文章中为大家解开谜题。