一句话总结:Vue3 的 Proxy 响应式系统已经解决了 Vue2 大部分“坑”,真正需要理解的是 依赖收集、Proxy identity、ref/reactive 边界和性能控制。
目录
- Vue3 响应式到底解决了什么
- Vue2 的坑在 Vue3 已经不存在
- Vue3 响应式核心原理(源码级理解)
- Vue3 真正需要注意的 10 个误区
- 大规模数据的性能优化策略
- 最佳实践总结
一、Vue3 响应式到底解决了什么
在 Vue2 中,响应式是基于
Object.defineProperty
实现的。
这带来几个经典问题:
| Vue2限制 | 原因 |
|---|---|
| 数组索引修改不更新 | defineProperty 无法拦截 |
| 对象新增属性 | 无法监听 |
| 深层对象性能差 | 递归 defineProperty |
需要 Vue.set | 手动触发 |
Vue3 使用:
ES6 Proxy
拦截:
get
set
deleteProperty
has
ownKeys
因此 这些限制已经全部消失。
二、Vue2 的坑在 Vue3 已经不存在
1 数组索引修改
Vue2:
list[0] = 10 // 不更新
Vue3:
list[0] = 10 // 完全响应式
因为 Proxy 能拦截:
set(target, key)
2 对象新增属性
Vue2:
Vue.set(obj, "age", 20)
Vue3:
obj.age = 20
即可。
3 深层对象更新
Vue2:
obj.a.b.c = 1
可能不会更新。
Vue3:
完全响应式。
三、Vue3 响应式核心原理
Vue3 响应式核心是三件事:
track
trigger
effect
结构如下:
targetMap (WeakMap)
target -> depsMap(Map)
key -> effects(Set)
结构示意:
WeakMap
|
|--- state
|
|--- "user" ----> Set(effect)
|
|--- "age" -----> Set(effect)
reactive 简化实现
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key)
const res = Reflect.get(target, key, receiver)
if (isObject(res)) {
return reactive(res)
}
return res
},
set(target, key, value, receiver) {
const old = target[key]
const result = Reflect.set(target, key, value, receiver)
if (old !== value) {
trigger(target, key)
}
return result
}
})
}
关键点:
嵌套对象在 get 时才 reactive。
这叫:
lazy reactive
四、Vue3 真正需要注意的 10 个误区
下面才是 Vue3 真正存在的问题。
1 reactive 解构会丢失响应性
const state = reactive({
user: { name: "Tom" }
})
const { user } = state
此时:
user 不是 Proxy
修改:
user.name = "Jack"
不会触发更新。
解决:
import { toRefs }
const { user } = toRefs(state)
2 reactive identity 问题
Proxy 会改变对象身份:
const obj = {}
const proxy = reactive(obj)
proxy === obj // false
因此:
map.set(obj, 1)
map.get(proxy) // undefined
解决:
使用:
toRaw()
3 ref 在模板会自动 unwrap
const count = ref(0)
模板:
{{ count }}
自动变成:
count.value
但在 JS 中不会。
count + 1 // 错
count.value + 1 // 对
4 reactive 不能直接替换对象
const state = reactive({ count: 0 })
state = reactive({ count: 10 }) // 错
因为:
state 是 const
正确方式:
Object.assign
5 watch 深度监听性能问题
watch(
state,
() => {},
{ deep: true }
)
如果数据巨大:
递归遍历整个对象
性能极差。
解决:
只监听需要的部分:
watch(() => state.user)
6 shallowRef 与 ref 的区别
ref:
深层 reactive
shallowRef:
只监听 value
示例:
const data = shallowRef({
deep: { value: 1 }
})
data.value.deep.value = 2
不会更新。
需要:
triggerRef(data)
7 markRaw 可以跳过响应式
某些对象不适合 Proxy:
例如:
- 大型库实例
- 地图对象
- DOM
可以使用:
markRaw(obj)
例如:
const chart = markRaw(new Chart())
8 不要对超大数据 reactive
例如:
10万条数据
reactive(bigList)
会创建大量 Proxy。
优化:
shallowRef
virtual list
9 computed 是 lazy 的
computed:
只有被访问才执行
例如:
const total = computed(() => price * count)
如果模板不使用:
不会计算
10 watch 默认异步执行
watch 默认 flush:
pre
顺序:
state change
watch
render
可以改成:
watch(source, cb, { flush: "post" })
五、大规模数据优化
真正影响性能的不是响应式正确性,而是 数据规模。
推荐策略:
1 虚拟滚动
只渲染:
可见 20 行
2 shallowRef
const list = shallowRef([])
3 markRaw
const hugeData = markRaw(data)
4 分页
不要 reactive 全量数据。
六、Vue3 响应式最佳实践
推荐原则:
1 基本类型用 ref
ref
2 对象用 reactive
reactive
3 避免 deep watch
watch specific path
4 大对象用 shallowRef
5 避免 reactive identity 问题
使用:
toRaw
总结
Vue3 的 Proxy 响应式已经解决了 Vue2 的绝大多数问题:
| Vue2问题 | Vue3 |
|---|---|
| 数组索引更新 | 已解决 |
| 对象新增属性 | 已解决 |
| 深层对象监听 | 已解决 |
| Vue.set | 不再需要 |
真正需要理解的是:
ref vs reactive
Proxy identity
shallow reactive
依赖收集
性能控制
理解这些,你就真正掌握了 Vue3 响应式系统。