返回

Vue源码之依赖收集,浅析 reactivity 本质

前端

在 Vue 的世界里,响应式数据是构建高效、动态界面的基石。而实现响应式的关键就在于依赖收集。所谓依赖收集,就是追踪所有依赖于某个响应式数据的组件或函数,以便在数据发生变化时及时通知它们进行更新。

在 Vue 的源码中,依赖收集的实现主要围绕一个叫 Dep 管家的角色展开。Dep 管家是一个类,负责管理与某个 key 相关的 Watcher 数组。当组件或函数访问某个响应式数据时,会触发数据的 get 方法,此时 Dep 管家便会将该组件或函数对应的 Watcher 实例添加到自己的数组中。

那么,问题来了,为什么我们需要 Dep 管家?这就要从 Vue 的数据劫持说起。在 Vue 中,所有响应式数据都会被劫持,也就是说,它们的 get 和 set 方法会被重写。当我们访问一个响应式数据时,实际上是调用了它的 get 方法。而在这个 get 方法中,会根据当前响应式数据的 key,找到与之关联的 Dep 管家,并将当前组件或函数对应的 Watcher 实例添加到该 Dep 管家的 Watcher 数组中。

这样一来,当响应式数据发生变化时,触发其 setter 方法,Dep 管家就会遍历自己的 Watcher 数组,依次调用这些 Watcher 实例的 update 方法,从而实现数据变化的响应式更新。

掌握了依赖收集的原理后,我们就可以构建属于自己的响应式系统了。这里以一个简单的例子来说明:

// 定义一个响应式数据对象
const data = {
  name: '张三',
  age: 18
}

// 创建一个 Dep 管家,与 key 'name' 相关联
const nameDep = new Dep()

// 创建一个组件,并将其添加到 nameDep 的 Watcher 数组中
const component = {
  template: `<div>{{ data.name }}</div>`,
  data() {
    return {
      data
    }
  },
  watch: {
    'data.name': function(newVal, oldVal) {
      // 当 data.name 发生变化时,触发此回调函数
      console.log('name changed from', oldVal, 'to', newVal)
    }
  },
  mounted() {
    // 手动触发依赖收集
    nameDep.add(this)
  }
}

// 触发 data.name 的 setter 方法,从而触发响应式更新
data.name = '李四'

在这个例子中,我们创建了一个响应式数据对象 data,并定义了一个与 key 'name' 相关联的 Dep 管家 nameDep。然后,我们创建了一个组件 component,并在组件的 mounted 钩子函数中手动触发依赖收集,将 component 添加到 nameDep 的 Watcher 数组中。当我们触发 data.name 的 setter 方法时,nameDep 会遍历自己的 Watcher 数组,依次调用 Watcher 实例的 update 方法,从而实现数据变化的响应式更新。

通过这个例子,我们可以看到,依赖收集是实现响应式系统的重要一环。掌握了依赖收集的原理,我们就可以构建出更加灵活、高效的响应式系统。