Skip to content

响应式原理

Vue 的响应式系统是其核心特性之一,它使得数据变化能够自动触发视图更新。本章将深入探讨 Vue 3 响应式系统的工作原理。

响应式基础

Vue 3 的响应式系统基于 ES6 的 Proxy 实现,相比 Vue 2 的 Object.defineProperty 有了显著的改进。

Proxy vs Object.defineProperty

javascript
// Vue 2 方式 (Object.defineProperty)
Object.defineProperty(obj, 'count', {
  get() {
    return value
  },
  set(newValue) {
    value = newValue
    // 触发更新
  }
})

// Vue 3 方式 (Proxy)
const proxy = new Proxy(obj, {
  get(target, key) {
    track(target, key) // 依赖收集
    return target[key]
  },
  set(target, key, value) {
    target[key] = value
    trigger(target, key) // 触发更新
    return true
  }
})

响应式 API

ref()

ref() 接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

javascript
import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0

count.value = 1
console.log(count.value) // 1

reactive()

reactive() 返回一个对象的响应式代理。

javascript
import { reactive } from 'vue'

const state = reactive({
  count: 0,
  name: 'Vue'
})

state.count++ // 响应式更新

readonly()

创建一个只读的响应式对象:

javascript
import { reactive, readonly } from 'vue'

const original = reactive({ count: 0 })
const copy = readonly(original)

// 修改 original 会触发依赖于 copy 的侦听器
original.count++

// 修改 copy 会失败并导致警告
copy.count++ // 警告!

依赖收集与触发

依赖收集 (track)

当响应式对象的属性被访问时,Vue 会收集当前正在执行的副作用函数作为依赖:

javascript
let activeEffect = null

function track(target, key) {
  if (activeEffect) {
    // 将当前副作用函数添加到依赖集合中
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = new Set()))
    }
    dep.add(activeEffect)
  }
}

触发更新 (trigger)

当响应式对象的属性被修改时,Vue 会触发所有相关的副作用函数:

javascript
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

计算属性的实现

计算属性是基于响应式依赖进行缓存的:

javascript
import { ref, computed } from 'vue'

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

count.value = 2
console.log(plusOne.value) // 3

计算属性的内部实现:

javascript
function computed(getter) {
  let value
  let dirty = true
  
  const effect = new ReactiveEffect(getter, () => {
    if (!dirty) {
      dirty = true
      // 触发计算属性的依赖更新
    }
  })
  
  return {
    get value() {
      if (dirty) {
        value = effect.run()
        dirty = false
      }
      return value
    }
  }
}

侦听器的实现

侦听器允许我们在响应式状态发生变化时执行副作用:

javascript
import { ref, watch } from 'vue'

const count = ref(0)

watch(count, (newValue, oldValue) => {
  console.log(`count 从 ${oldValue} 变为 ${newValue}`)
})

响应式系统的优势

  1. 更好的性能:Proxy 可以监听整个对象,而不需要遍历所有属性
  2. 支持数组索引和长度变化:可以检测数组的索引赋值和长度变化
  3. 支持 Map、Set、WeakMap、WeakSet:原生支持更多数据结构
  4. 更好的类型推导:TypeScript 支持更好

响应式系统的限制

虽然 Vue 3 的响应式系统很强大,但仍有一些限制需要注意:

  1. 解构赋值会失去响应性
javascript
const state = reactive({ count: 0 })
let { count } = state // count 不再是响应式的
  1. 将响应式对象的属性赋值给本地变量
javascript
const state = reactive({ count: 0 })
let count = state.count // count 不是响应式的

最佳实践

  1. 优先使用 ref() 处理基本类型
  2. 使用 reactive() 处理对象类型
  3. 使用 toRefs() 解构响应式对象
  4. 避免直接解构响应式对象
javascript
import { reactive, toRefs } from 'vue'

const state = reactive({ count: 0, name: 'Vue' })

// 正确的解构方式
const { count, name } = toRefs(state)

下一步

vue study guide - 专业的 Vue.js 学习平台