Skip to content
alex's blog
Go back

Vue3 响应式系统深度解析

编辑页面

Vue3 的响应式系统是其核心特性之一,它基于 ES6 的 Proxy API 重新设计,相比 Vue2 的 Object.defineProperty 有了显著的性能提升和功能增强。本文将深入解析 Vue3 响应式系统的工作原理。

Vue2 vs Vue3 响应式系统

Vue2 的局限性

Vue2 使用 Object.defineProperty 来实现响应式:

// Vue2 的实现方式
Object.defineProperty(obj, 'key', {
  get() {
    // 依赖收集
    return value
  },
  set(newVal) {
    // 触发更新
    value = newVal
  }
})

存在的问题:

  1. 无法监听数组索引和长度的变化
  2. 无法监听对象属性的添加和删除
  3. 需要递归遍历对象的所有属性
  4. 性能开销较大

Vue3 的优势

Vue3 使用 Proxy 来实现响应式:

// Vue3 的实现方式
const proxy = new Proxy(obj, {
  get(target, key) {
    // 依赖收集
    return target[key]
  },
  set(target, key, value) {
    // 触发更新
    target[key] = value
    return true
  }
})

优势:

  1. 可以监听数组索引和长度变化
  2. 可以监听对象属性的添加和删除
  3. 性能更好,按需响应
  4. 支持 Map、Set、WeakMap、WeakSet

核心概念

1. Reactive 函数

reactive 函数将普通对象转换为响应式对象:

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: {
    name: 'John',
    age: 30
  }
})

// 所有嵌套属性都是响应式的
state.count++ // 触发更新
state.user.name = 'Jane' // 触发更新

内部实现原理:

function reactive(target) {
  // 如果已经是响应式对象,直接返回
  if (isReactive(target)) {
    return target
  }
  
  // 创建 Proxy
  return new Proxy(target, {
    get(target, key, receiver) {
      // 依赖收集
      track(target, key)
      
      const result = Reflect.get(target, key, receiver)
      
      // 如果值是对象,递归转换为响应式
      if (isObject(result)) {
        return reactive(result)
      }
      
      return result
    },
    
    set(target, key, value, receiver) {
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      
      // 触发更新
      if (oldValue !== value) {
        trigger(target, key)
      }
      
      return result
    },
    
    deleteProperty(target, key) {
      const hadKey = hasOwn(target, key)
      const result = Reflect.deleteProperty(target, key)
      
      if (hadKey) {
        trigger(target, key)
      }
      
      return result
    }
  })
}

2. Ref 函数

ref 用于创建基本类型的响应式数据:

import { ref } from 'vue'

const count = ref(0)

// ref 内部实现
function ref(value) {
  return {
    _value: value,
    get value() {
      track(this, 'value')
      return this._value
    },
    set value(newValue) {
      if (this._value !== newValue) {
        this._value = newValue
        trigger(this, 'value')
      }
    }
  }
}

3. 依赖收集(Track)

当访问响应式数据时,Vue 会收集当前的副作用函数:

// 简化的依赖收集实现
const targetMap = new WeakMap()

function track(target, key) {
  // 获取当前正在执行的副作用函数
  const effect = activeEffect
  
  if (!effect) return
  
  // 获取 target 的依赖映射
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  
  // 获取 key 的依赖集合
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  
  // 添加依赖
  dep.add(effect)
}

4. 触发更新(Trigger)

当修改响应式数据时,Vue 会触发相关的副作用函数:

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

5. 副作用函数(Effect)

副作用函数是响应式系统的核心,它会在依赖变化时重新执行:

let activeEffect = null

function effect(fn, options = {}) {
  const effectFn = () => {
    // 清理旧的依赖
    cleanup(effectFn)
    
    // 设置为当前活跃的副作用
    activeEffect = effectFn
    effectStack.push(effectFn)
    
    // 执行函数
    const result = fn()
    
    // 恢复之前的副作用
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
    
    return result
  }
  
  effectFn.deps = []
  effectFn.options = options
  
  if (options.lazy) {
    return effectFn
  }
  
  effectFn()
  return effectFn
}

实际应用

示例 1: 计算属性的实现

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

示例 2: Watch 的实现

function watch(source, cb, options = {}) {
  let getter
  if (typeof source === 'function') {
    getter = source
  } else {
    getter = () => traverse(source)
  }
  
  let oldValue, newValue
  const job = () => {
    newValue = effectFn()
    cb(newValue, oldValue)
    oldValue = newValue
  }
  
  const effectFn = effect(() => getter(), {
    lazy: true,
    scheduler: job
  })
  
  if (options.immediate) {
    job()
  } else {
    oldValue = effectFn()
  }
}

性能优化

1. 浅层响应式

对于不需要深度响应式的大对象,可以使用 shallowReactive

import { shallowReactive } from 'vue'

const state = shallowReactive({
  count: 0,
  user: {
    name: 'John' // 这个属性不是响应式的
  }
})

2. 只读响应式

使用 readonly 创建只读的响应式对象:

import { readonly } from 'vue'

const state = readonly({
  count: 0
})

state.count++ // 警告:无法修改只读属性

3. 标记原始对象

使用 markRaw 标记对象,使其永远不会被转换为响应式:

import { markRaw, reactive } from 'vue'

const foo = markRaw({})
const state = reactive({
  nested: foo // foo 不会被转换为响应式
})

常见问题

1. 为什么 ref 需要 .value?

因为基本类型无法使用 Proxy,所以 ref 将值包装在一个对象中:

const count = ref(0)
// 实际上相当于
const count = { value: 0 }

2. reactive 和 ref 的区别?

3. 如何避免响应式丢失?

不要解构响应式对象:

// ❌ 错误:解构会丢失响应式
const { count } = reactive({ count: 0 })

// ✅ 正确:使用 toRefs
const state = reactive({ count: 0 })
const { count } = toRefs(state)

总结

Vue3 的响应式系统基于 Proxy 实现,相比 Vue2 有了显著的改进:

  1. 更好的性能:按需响应,不需要递归遍历
  2. 更强大的功能:支持数组索引、对象属性添加/删除
  3. 更好的类型支持:与 TypeScript 配合更好

理解响应式系统的工作原理,有助于我们更好地使用 Vue3,编写出更高效的代码。


深入理解响应式系统是掌握 Vue3 的关键。希望这篇文章能帮助你更好地理解 Vue3 的响应式机制!


编辑页面
Share this post on:

Previous Post
Vue3 性能优化技巧与实践
Next Post
Vue3 Composition API 入门指南