响应式系统重构(Proxy替代defineProperty)
响应式系统重构(Proxy替代defineProperty)
Vue.js 2.x 使用 Object.defineProperty
实现响应式系统,而 Vue 3 则改用 Proxy
重构了整个响应式机制。这种改变带来了性能提升和功能增强,同时也解决了 defineProperty
的一些固有缺陷。
defineProperty 的局限性
Object.defineProperty
存在几个关键问题:
- 无法检测属性的添加或删除:
const obj = {}
Object.defineProperty(obj, 'a', {
get() { return this._a },
set(val) {
console.log('set a')
this._a = val
}
})
obj.a = 1 // 触发 set
obj.b = 2 // 不会触发任何响应
- 数组变异方法需要特殊处理:
const arr = []
Object.defineProperty(arr, 'length', { /* ... */ })
arr.push(1) // 不会触发 length 的 setter
- 性能开销较大,需要递归遍历对象的所有属性进行转换。
Proxy 的优势
ES6 的 Proxy 可以完美解决这些问题:
const handler = {
get(target, key) {
console.log(`Get ${key}`)
return Reflect.get(target, key)
},
set(target, key, value) {
console.log(`Set ${key} = ${value}`)
return Reflect.set(target, key, value)
},
deleteProperty(target, key) {
console.log(`Delete ${key}`)
return Reflect.deleteProperty(target, key)
}
}
const proxy = new Proxy({}, handler)
proxy.a = 1 // 输出 "Set a = 1"
proxy.a // 输出 "Get a"
delete proxy.a // 输出 "Delete a"
Vue 3 的响应式实现
Vue 3 的 reactive 实现核心:
function reactive(target) {
const handler = {
get(target, key, receiver) {
track(target, key) // 依赖收集
const res = Reflect.get(target, key, receiver)
if (typeof res === 'object' && res !== null) {
return reactive(res) // 深层响应
}
return res
},
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
}
}
return new Proxy(target, handler)
}
性能对比
-
初始化性能:
- defineProperty: 需要递归遍历所有属性
- Proxy: 只在访问时处理,惰性转换
-
内存占用:
- defineProperty: 需要为每个属性创建闭包存储依赖
- Proxy: 整个对象共享一个处理器
-
数组处理:
const arr = reactive([1, 2, 3])
arr.push(4) // 正常触发响应
arr.length = 10 // 也能触发响应
实际应用场景
- 动态属性:
const state = reactive({})
// 可以动态添加响应式属性
state.newProp = 'value'
- Map/Set 支持:
const map = reactive(new Map())
map.set('key', 'value') // 响应式更新
- 更好的TS支持:
interface State {
count: number
user?: {
name: string
}
}
const state: State = reactive({
count: 0
})
// 类型安全访问
state.count++
兼容性考虑
虽然 Proxy 是现代浏览器的特性,但 Vue 3 提供了降级方案:
function createReactive(target) {
return ProxySupported
? new Proxy(target, handler)
: fallbackDefineProperty(target)
}
对于需要支持 IE 的场景,可以使用 @vue/compat
兼容版本。
响应式API的演进
Vue 3 提供了一系列新的响应式API:
import { reactive, ref, computed, watchEffect } from 'vue'
const count = ref(0)
const double = computed(() => count.value * 2)
const state = reactive({ list: [] })
watchEffect(() => {
console.log(`count is ${count.value}`)
})
与Vue 2的对比
- 数组检测:
// Vue 2
this.$set(this.arr, index, value)
// Vue 3
arr[index] = value // 直接赋值即可
- 嵌套对象:
// Vue 2
this.$set(this.obj, 'nested', {})
// Vue 3
obj.nested = {} // 自动响应
源码解析
简化版的依赖收集实现:
const targetMap = new WeakMap()
function track(target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
effects && effects.forEach(effect => effect.run())
}
最佳实践
- 大型对象处理:
// 避免一次性响应式化大型对象
const bigData = shallowReactive({ /* 大数据 */ })
- 性能敏感场景:
// 使用 markRaw 跳过响应式转换
import { markRaw } from 'vue'
const nonReactive = markRaw({ shouldNotTrack: true })
- 组合式函数:
function useCounter() {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, double, increment }
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn