• Vue2到Vue3进阶:第二篇 - 响应式系统深度解析

Vue2到Vue3进阶:第二篇 - 响应式系统深度解析

2025-06-23 02:57:35 1 阅读

本篇将深入探讨Vue3的响应式系统,从原理到实战全面解析ref、reactive等核心API,帮助你彻底掌握Vue3的响应式机制

一、Vue2响应式系统回顾与局限

1.1 Vue2的Object.defineProperty实现

// Vue2响应式简化实现
function defineReactive(obj, key) {
  let value = obj[key]
  const dep = new Dep() // 依赖收集器
  
  Object.defineProperty(obj, key, {
    get() {
      dep.depend() // 收集依赖
      return value
    },
    set(newVal) {
      if (newVal === value) return
      value = newVal
      dep.notify() // 通知更新
    }
  })
}
 

1.2 Vue2响应式的局限

  • 无法检测新增/删除的属性:需要Vue.set/Vue.delete

  • 数组索引修改不触发更新:需要特殊处理

  • 性能开销大:每个属性都需要单独劫持

  • 不支持Map、Set等集合类型

二、Vue3响应式原理:Proxy的革命

2.1 Proxy基础概念

const target = { count: 0 }
const handler = {
  get(target, key) {
    console.log(`读取属性: ${key}`)
    return tarGET@[key]
  },
  set(target, key, value) {
    console.log(`设置属性: ${key} = ${value}`)
    tarGET@[key] = value
    return true
  }
}

const proxy = new Proxy(target, handler)
proxy.count = 1 // 设置属性: count = 1
console.log(proxy.count) // 读取属性: count → 1

2.2 Vue3响应式核心实现

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key) // 依赖收集
      return Reflect.get(target, key)
    },
    set(target, key, value) {
      Reflect.set(target, key, value)
      trigger(target, key) // 触发更新
      return true
    }
  })
}
 

三、核心API深度解析

3.1 ref vs reactive 选择指南

特性refreactive
数据类型基本类型+对象仅对象类型
访问方式需要.value直接访问
模板自动解包支持不支持
替换整个对象需要重新赋值直接赋值
适用场景基本类型、DOM引用复杂数据结构、表单对象

3.2 ref高级用法

 

3.3 reactive使用技巧

 

四、响应式工具函数实战

4.1 toRefs:解构响应式对象



 

4.2 readonly:创建只读对象

import { reactive, readonly } from 'vue'

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

original.count++ // 成功
copy.count++ // 失败: Set operation on key "count" failed
 

4.3 computed:计算属性进阶

 

五、响应式进阶技巧

5.1 响应式数组操作

const list = reactive(['a', 'b'])

// 正确方法
list.push('c') 
list.splice(0, 1)

// 错误方法(不会触发更新)
list[0] = 'x' // 无效
list.length = 0 // 无效
 

5.2 响应式Map/Set使用

import { reactive } from 'vue'

const map = reactive(new Map())
map.set('name', 'Vue3')

const set = reactive(new Set())
set.add(1)
 

5.3 深层响应式转换

// 默认是深层的
const deep = reactive({ 
  nested: { 
    value: 1 
  }
})

// 创建浅层响应式
import { shallowReactive } from 'vue'
const shallow = shallowReactive({
  nested: { value: 1 } // 嵌套对象不是响应式的
})
 

六、性能优化技巧

6.1 避免不必要的响应式转换

// 不良实践:整个大数组转为响应式
const largeArray = reactive([...]) 

// 良好实践:只对需要交互的部分使用响应式
const activeItem = reactive({})
 

6.2 使用shallowRef/shallowReactive

// 大数据对象优化
const bigData = shallowRef({ 
  /* 包含大量数据但很少变更的对象 */ 
})

// 触发更新
bigData.value = { ...bigData.value, updatedProp: 'new' }
 

6.3 使用markRaw避免响应式转换

import { reactive, markRaw } from 'vue'

const state = reactive({
  // 标记为原始对象,不会被转换为响应式
  staticData: markRaw(bigStaticData)
})
 

七、响应式原理实践:自定义响应式对象

7.1 实现简单的响应式系统

function createReactiveObject(target) {
  const depsMap = new Map()
  
  return new Proxy(target, {
    get(target, key) {
      // 依赖收集
      track(target, key)
      return tarGET@[key]
    },
    set(target, key, value) {
      tarGET@[key] = value
      // 触发更新
      trigger(target, key)
      return true
    }
  })
  
  function track(target, key) {
    // 实际实现会更复杂
    console.log(`收集依赖: ${key}`)
  }
  
  function trigger(target, key) {
    console.log(`触发更新: ${key}`)
  }
}

const obj = createReactiveObject({ count: 0 })
obj.count++ // 触发更新: count
 

八、常见问题解答

Q1:为什么需要.value访问ref的值?

A:因为基本类型(如Number)在JavaScript中不是对象,无法通过Proxy代理,因此Vue使用对象包装器来实现响应性

Q2:什么时候该用ref,什么时候用reactive?

  • 基本类型 → ref

  • 对象类型且不需要替换整个对象 → reactive

  • 需要替换整个对象 → ref

  • 需要传递到组合函数 → ref(保持引用一致性)

Q3:如何检测响应式对象?

import { isRef, isReactive, isProxy } from 'vue'

const count = ref(0)
const state = reactive({})

console.log(isRef(count)) // true
console.log(isReactive(state)) // true
console.log(isProxy(state)) // true (所有响应式对象都是Proxy)
 

九、最佳实践总结

  1. 优先使用