Vue3响应式原理:深入理解Proxy和Ref
Vue3响应式原理:深入理解Proxy和Ref
前言
各位前端小伙伴,不知道你们有没有好奇过:Vue的响应式系统是怎么工作的?为什么数据变化会自动更新视图?
我曾经对这个问题非常好奇,后来深入研究了Vue3的源码,终于搞懂了它的实现原理!今天就来分享给大家。
Vue2 vs Vue3响应式
Vue2的Object.defineProperty
// Vue2的响应式实现 function defineReactive(obj, key, val) { Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { // 依赖收集 Dep.target && dep.addSub(Dep.target) return val }, set(newVal) { if (newVal === val) return val = newVal // 触发更新 dep.notify() } }) }缺点:
- 无法检测对象属性的添加和删除
- 无法检测数组元素的变化
- 需要递归遍历对象
Vue3的Proxy
// Vue3的响应式实现 function reactive(target) { 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 result = Reflect.set(target, key, value, receiver) // 触发更新 trigger(target, key) return result }, deleteProperty(target, key) { const result = Reflect.deleteProperty(target, key) // 触发更新 trigger(target, key) return result } }) }优点:
- 可以检测对象属性的添加和删除
- 可以检测数组元素的变化
- 性能更好,无需递归遍历
Proxy工作原理
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 数据对象 │ │ Proxy │ │ 副作用函数 │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ │ 1. 创建Proxy代理 │ │ │───────────────────────>│ │ │ │ │ │ │ 2. 访问属性触发get │ │<───────────────────────│ │ │ │ │ │ │ │ 3. 依赖收集 │ │ │────────────────────────>│ │ │ │ │ │ 4. 修改属性触发set │ │<───────────────────────│ │ │ │ │ │ │ │ 5. 触发更新 │ │ │<────────────────────────│ref和reactive的区别
ref的实现
class RefImpl { constructor(value) { this._value = value this._dep = new Dep() } get value() { // 依赖收集 trackRefValue(this) return this._value } set value(newVal) { if (hasChanged(newVal, this._value)) { this._value = newVal // 触发更新 triggerRefValue(this) } } } function ref(value) { return new RefImpl(value) }reactive的实现
function reactive(target) { // 如果已经是响应式对象,直接返回 if (isReactive(target)) { return target } const proxy = new Proxy(target, { get(target, key, receiver) { // 依赖收集 track(target, key) const result = Reflect.get(target, key, receiver) // 递归处理嵌套对象 if (isObject(result) && !isReactive(result)) { return reactive(result) } return result }, set(target, key, value, receiver) { const oldValue = target[key] const result = Reflect.set(target, key, value, receiver) if (hasChanged(value, oldValue)) { // 触发更新 trigger(target, key) } return result } }) return proxy }依赖收集和触发更新
track函数
const targetMap = new WeakMap() function track(target, key) { // 获取当前活跃的副作用函数 const effect = activeEffect if (!effect) return // 获取target对应的Map 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) } // 将副作用函数添加到依赖集合 if (!dep.has(effect)) { dep.add(effect) effect.deps.push(dep) } }trigger函数
function trigger(target, key) { // 获取target对应的依赖Map const depsMap = targetMap.get(target) if (!depsMap) return // 获取key对应的依赖集合 const dep = depsMap.get(key) if (!dep) return // 执行所有依赖的副作用函数 const effects = new Set(dep) effects.forEach(effect => { effect() }) }响应式实战
创建响应式对象
import { reactive, ref } from 'vue' // reactive用于对象 const state = reactive({ name: 'Vue', version: 3 }) // ref用于基本类型 const count = ref(0) // 修改数据会自动触发更新 state.name = 'Vue 3' count.value++嵌套响应式
const state = reactive({ user: { name: 'John', address: { city: 'Beijing' } } }) // 嵌套对象也会被响应式处理 state.user.name = 'Jane' state.user.address.city = 'Shanghai'数组响应式
const list = reactive([1, 2, 3]) // 数组操作也会触发更新 list.push(4) list.pop() list[0] = 100响应式高级用法
shallowReactive
import { shallowReactive } from 'vue' const state = shallowReactive({ nested: { count: 0 } }) // 顶层属性变化会触发更新 state.nested = { count: 1 } // 嵌套属性变化不会触发更新 state.nested.count++shallowRef
import { shallowRef } from 'vue' const state = shallowRef({ count: 0 }) // 修改引用会触发更新 state.value = { count: 1 } // 修改属性不会触发更新 state.value.count++readonly
import { readonly } from 'vue' const state = reactive({ count: 0 }) const readOnlyState = readonly(state) // 尝试修改会警告 readOnlyState.count++ // Warning: Set operation on key "count" failedwatchEffect
import { ref, watchEffect } from 'vue' const count = ref(0) watchEffect(() => { console.log('Count changed:', count.value) }) count.value++ // 输出: Count changed: 1 count.value++ // 输出: Count changed: 2响应式原理常见问题
问题1:为什么ref需要.value?
原因:
- JavaScript的基本类型(string、number、boolean)是值传递
- Proxy无法代理基本类型
- ref通过包装对象来实现响应式
问题2:为什么数组索引修改不触发更新?
解决方案:
- Vue3已经支持数组索引修改
- 确保使用reactive创建数组
问题3:为什么对象新增属性不触发更新?
解决方案:
- Vue3使用Proxy,支持新增属性
- 确保使用reactive创建对象
响应式性能优化
1. 使用shallowReactive优化大型对象
// 对于不需要深层响应式的大型对象 const largeObject = shallowReactive(largeData)2. 使用ref替代reactive
// ref性能更好,因为不需要Proxy const count = ref(0) const name = ref('Vue')3. 避免在循环中创建响应式对象
// 不好的做法 items.forEach(item => { item.data = reactive(item.data) }) // 好的做法 const data = reactive(items.map(item => item.data))总结
Vue3的响应式系统基于Proxy实现,相比Vue2有很大改进:
- 更强大:支持对象属性的添加和删除
- 更高效:无需递归遍历
- 更灵活:支持多种响应式类型
现在,你应该对Vue3的响应式原理有了深入的理解!快去试试吧!
最后一句忠告:不要过度使用响应式,只在需要的地方使用!
