Vue 3 响应式原理详解

Vue 3 的响应式系统完全重写,使用 Proxy 替代了 Vue 2 的 Object.defineProperty,提供了更好的性能和更强大的功能。

1. 核心原理概述

Vue 2 的局限性

// Vue 2 使用 Object.defineProperty
Object.defineProperty(obj, 'key', {
  get() {
    // 依赖收集
    return value
  },
  set(newVal) {
    // 触发更新
    value = newVal
  }
})

问题

  • 无法检测对象属性的添加/删除
  • 数组变异方法需要重写
  • 需要递归遍历整个对象
  • 性能开销较大

Vue 3 的解决方案 - Proxy

const reactive = (target) => {
  return new Proxy(target, {
    get(obj, key, receiver) {
      // 依赖收集
      const result = Reflect.get(obj, key, receiver)
      return result
    },
    set(obj, key, value, receiver) {
      // 触发更新
      const result = Reflect.set(obj, key, value, receiver)
      return result
    },
    deleteProperty(obj, key) {
      // 处理属性删除
      const result = Reflect.deleteProperty(obj, key)
      return result
    }
  })
}

2. 响应式系统三大核心

2.1 响应式对象创建 (reactive)

// 简化版的 reactive 实现
function reactive(target) {
  return new Proxy(target, {
    get(obj, key, receiver) {
      // 追踪依赖
      track(obj, key)
      
      const result = Reflect.get(obj, key, receiver)
      
      // 如果是对象,递归代理
      if (typeof result === 'object' && result !== null) {
        return reactive(result)
      }
      
      return result
    },
    
    set(obj, key, value, receiver) {
      const oldValue = obj[key]
      const result = Reflect.set(obj, key, value, receiver)
      
      // 只有值发生变化时才触发更新
      if (oldValue !== value) {
        trigger(obj, key)
      }
      
      return result
    },
    
    deleteProperty(obj, key) {
      const hadKey = Object.prototype.hasOwnProperty.call(obj, key)
      const result = Reflect.deleteProperty(obj, key)
      
      if (hadKey) {
        trigger(obj, key)
      }
      
      return result
    }
  })
}

2.2 依赖收集 (track)

// 存储所有依赖关系的全局变量
const targetMap = new WeakMap()
let activeEffect = null

function track(target, key) {
  if (!activeEffect) return
  
  // 获取 target 对应的依赖映射
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  
  // 获取 key 对应的依赖集合
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  
  // 添加当前活动的 effect
  dep.add(activeEffect)
}

2.3 触发更新 (trigger)

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  const dep = depsMap.get(key)
  if (dep) {
    // 执行所有相关的 effect
    dep.forEach(effect => {
      if (effect.scheduler) {
        effect.scheduler()
      } else {
        effect()
      }
    })
  }
}

3. 完整的响应式系统实现

3.1 Effect 系统

// 响应式副作用系统
function effect(fn, options = {}) {
  const effectFn = () => {
    // 清理依赖
    cleanup(effectFn)
    
    // 设置当前活动的 effect
    activeEffect = effectFn
    effectStack.push(effectFn)
    
    // 执行原始函数
    const result = fn()
    
    // 恢复之前的 effect
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
    
    return result
  }
  
  // 存储依赖
  effectFn.deps = []
  effectFn.options = options
  
  // 懒执行
  if (!options.lazy) {
    effectFn()
  }
  
  return effectFn
}

function cleanup(effectFn) {
  for (const dep of effectFn.deps) {
    dep.delete(effectFn)
  }
  effectFn.deps.length = 0
}

3.2 计算属性 (computed)

function computed(getter) {
  let value
  let dirty = true
  
  const effectFn = effect(getter, {
    lazy: true,
    scheduler() {
      dirty = true
      trigger(obj, 'value')
    }
  })
  
  const obj = {
    get value() {
      if (dirty) {
        value = effectFn()
        dirty = false
      }
      track(obj, 'value')
      return value
    }
  }
  
  return obj
}

3.3 引用类型响应式 (ref)

function ref(value) {
  return {
    get value() {
      track(this, 'value')
      return value
    },
    set value(newVal) {
      if (newVal !== value) {
        value = newVal
        trigger(this, 'value')
      }
    },
    __v_isRef: true
  }
}

4. 实际应用示例

4.1 响应式数据流

import { reactive, effect } from 'vue'

const state = reactive({
  count: 0,
  message: 'Hello'
})

// 副作用函数 - 自动追踪依赖
effect(() => {
  console.log(`Count: ${state.count}, Message: ${state.message}`)
})

// 触发更新
state.count = 1  // 输出: Count: 1, Message: Hello
state.message = 'World'  // 输出: Count: 1, Message: World

4.2 计算属性实现

import { reactive, computed, effect } from 'vue'

const state = reactive({
  price: 10,
  quantity: 2
})

const total = computed(() => state.price * state.quantity)

effect(() => {
  console.log(`Total: $${total.value}`)
})

state.price = 20  // 输出: Total: $40
state.quantity = 3  // 输出: Total: $60

5. 性能优化特性

5.1 懒代理

function reactive(target) {
  return new Proxy(target, {
    get(obj, key, receiver) {
      track(obj, key)
      
      const result = Reflect.get(obj, key, receiver)
      
      // 只有访问时才进行代理,不是一次性递归代理所有
      if (typeof result === 'object' && result !== null) {
        return reactive(result)
      }
      
      return result
    }
    // ... 其他 trap
  })
}

5.2 调度系统

const queue = new Set()
let isFlushing = false

function queueJob(job) {
  queue.add(job)
  if (!isFlushing) {
    isFlushing = true
    Promise.resolve().then(() => {
      try {
        queue.forEach(job => job())
      } finally {
        queue.clear()
        isFlushing = false
      }
    })
  }
}

6. 与 Vue 2 的对比

特性Vue 2 (Object.defineProperty)Vue 3 (Proxy)
检测属性添加/删除❌ 需要 Vue.set/Vue.delete✅ 原生支持
数组索引修改❌ 需要重写方法✅ 原生支持
数组长度修改❌ 无法检测✅ 原生支持
Map/Set 支持❌ 有限支持✅ 完整支持
性能递归遍历,初始化慢懒代理,按需响应
内存占用较高较低

7. 总结

Vue 3 的响应式原理核心:

  1. Proxy 代理:使用 ES6 Proxy 拦截对象操作
  2. 依赖收集:在 get 操作时收集依赖的 effect
  3. 触发更新:在 set 操作时触发相关的 effect 重新执行
  4. Effect 系统:管理副作用的执行和清理
  5. 性能优化:懒代理、调度队列、缓存等机制

这种设计使得 Vue 3 的响应式系统更强大、性能更好,并且能够处理更多复杂场景。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐