Vue2到Vue3进阶:第二篇 - 响应式系统深度解析
本篇将深入探讨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 选择指南
特性 | ref | reactive |
---|---|---|
数据类型 | 基本类型+对象 | 仅对象类型 |
访问方式 | 需要.value | 直接访问 |
模板自动解包 | 支持 | 不支持 |
替换整个对象 | 需要重新赋值 | 直接赋值 |
适用场景 | 基本类型、DOM引用 | 复杂数据结构、表单对象 |
3.2 ref高级用法
3.3 reactive使用技巧
四、响应式工具函数实战
4.1 toRefs:解构响应式对象
{{ count }} - {{ name }}
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)
九、最佳实践总结
-
优先使用
语法:简化代码结构
-
组合式函数返回ref对象:保持响应性
-
避免直接解构reactive对象:使用toRefs
-
合理使用计算属性:减少重复计算
-
性能敏感场景使用浅层响应式:shallowRef/shallowReactive