Vue 响应式原理

Vue 响应式原理

前言

本文为想学习 Vue 响应式系统的前端开发者准备,涵盖 refreactive 的工作原理及其在 Vue 3 中的实现机制。

为什么学习响应式原理

学习 Vue 的响应式原理可以帮助开发者更好地理解框架的内部机制,从而在实际开发中更有效地利用这些特性。掌握响应式原理有助于:

  • 提高调试能力:了解数据是如何变化的,可以更快地定位问题。
  • 优化性能:通过合理使用响应式 API,减少不必要的渲染和计算。
  • 增强代码可读性:清晰的理解数据流动,有助于编写更易于维护的代码。

Vue 响应式系统概述

Vue 3 的响应式系统基于 ES6 的 Proxy 对象实现,主要通过 refreactive 两个 API 来创建响应式数据。

  • ref:用于创建基本类型的响应式数据,如字符串、数字、布尔值等。它返回一个包含 .value 属性的对象。
  • reactive:用于创建复杂类型的响应式数据,如对象和数组。

这两个 API 都会追踪数据的变化,并在数据更新时通知相关的组件进行重新渲染。

ref 的工作原理

ref 函数接受一个初始值,并返回一个包含该值的响应式对象。其核心原理如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function ref(initialValue) {
  const r = {
    get value() {
      // 依赖收集
      track(r, 'value')
      return initialValue
    },
    set value(newValue) {
      initialValue = newValue
      // 触发更新
      trigger(r, 'value')
    }
  }
  return r
}
  • 依赖收集:当访问 ref.value 属性时,会调用 track 函数,将当前的副作用函数(如组件的渲染函数)与该属性关联起来。
  • 触发更新:当修改 .value 属性时,会调用 trigger 函数,通知所有依赖该属性的副作用函数重新执行。

reactive 的工作原理

reactive 函数接受一个对象,并返回该对象的响应式代理。其核心原理如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function reactive(target) {
  return new Proxy(target, {
    get(target, key) {
      // 依赖收集
      track(target, key)
      return Reflect.get(target, key)
    },
    set(target, key, value) {
      const result = Reflect.set(target, key, value)
      // 触发更新
      trigger(target, key)
      return result
    }
  })
}
  • 依赖收集:当访问对象的属性时,会调用 track 函数,将当前的副作用函数与该属性关联起来。
  • 触发更新:当修改对象的属性时,会调用 trigger 函数,通知所有依赖该属性的副作用函数重新执行。

依赖追踪与副作用函数

Vue 使用一个全局的 activeEffect 变量来追踪当前正在执行的副作用函数。当副作用函数执行时,activeEffect 会被设置为该函数。当访问响应式数据时,track 函数会将 activeEffect 与该数据关联起来。

1
2
3
4
5
6
7
8
9
let activeEffect = null
function effect(fn) {
  const effectFn = () => {
    activeEffect = effectFn
    fn()
    activeEffect = null
  }
  effectFn()
}
  • effect 函数:接受一个函数作为参数,并将其包装为副作用函数。执行该函数时,会设置 activeEffect,从而实现依赖收集。
  • track 函数:将 activeEffect 与访问的属性关联起来,存储在一个依赖集合中。
  • trigger 函数:当属性值发生变化时,遍历依赖集合,执行所有关联的副作用函数。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const targetMap = new WeakMap()
function track(target, key) {
  if (!activeEffect) return
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  dep.add(activeEffect)
}
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}
  • targetMap:使用 WeakMap 存储每个响应式对象及其属性的依赖集合。
  • track 函数:将当前的 activeEffect 添加到对应属性的依赖集合中。
  • trigger 函数:遍历属性的依赖集合,执行所有副作用函数。

refreactive 的区别与联系

  • 数据类型ref 适用于基本类型数据,而 reactive 适用于对象和数组。
  • 访问方式ref 需要通过 .value 访问和修改值,而 reactive 可以直接通过属性访问和修改。
  • 嵌套响应式reactive 会递归地将嵌套对象转换为响应式,而 ref 只会对其初始值进行响应式处理。
  • 性能ref 在处理大量基本类型数据时,性能可能优于 reactive,因为后者需要处理更多的代理逻辑。
  • 使用注意reactive底层是通过Proxy实现的,因此不能直接对reactive对象进行解构赋值,否则会失去响应式特性,同样它也不能监听原始数据类型。相反,ref可以安全地解构,因为它返回的是一个包含.value属性的对象。

结语

通过学习 Vue 的响应式原理,开发者可以更深入地理解框架的工作机制,从而在实际开发中更有效地利用这些特性。掌握 refreactive 的实现原理,有助于提高调试能力、优化性能,并编写更易于维护的代码。

参考资料

Licensed under CC BY-NC-SA 4.0