前言
大家都知道,Vue2 里的响应式其实有点像是一个半完全体,对于对象上新增的属性无能为力,对于数组则需要拦截它的原型方法来实现响应式。
举个例子:
代码语言:javascript复制let vm = new Vue({
data() {
return {
a: 1
}
}
})
// ❌ oops,没反应!
vm.b = 2
代码语言:javascript复制let vm = new Vue({
data() {
return {
a: 1
}
},
watch: {
b() {
console.log('change !!')
}
}
})
// ❌ oops,没反应!
vm.b = 2
这种时候,Vue 提供了一个 api:this.$set
,来使得新增的属性也拥有响应式的效果。
但是对于很多新手来说,很多时候需要小心翼翼的去判断到底什么情况下需要用 $set
,什么时候可以直接触发响应式。
总之,在 Vue3 中,这些都将成为过去。本篇文章会带你仔细讲解,proxy 到底会给 Vue3 带来怎么样的便利。并且会从源码级别,告诉你这些都是如何实现的。
响应式仓库
Vue3 不同于 Vue2 也体现在源码结构上,Vue3 把耦合性比较低的包分散在 packages
目录下单独发布成 npm
包。 这也是目前很流行的一种大型项目管理方式 Monorepo
。
其中负责响应式部分的仓库就是 @vue/reactivity,它不涉及 Vue 的其他的任何部分,是非常非常 「正交」 的一种实现方式。
甚至可以轻松的集成进 React。
这也使得本篇的分析可以更加聚焦的分析这一个仓库,排除其他无关部分。
区别
Proxy 和 Object.defineProperty 的使用方法看似很相似,其实 Proxy 是在 「更高维度」 上去拦截属性的修改的,怎么理解呢?
Vue2 中,对于给定的 data,如 { count: 1 }
,是需要根据具体的 key 也就是 count
,去对「修改 data.count 」 和 「读取 data.count」进行拦截,也就是
Object.defineProperty(data, 'count', {
get() {},
set() {},
})
必须预先知道要拦截的 key 是什么,这也就是为什么 Vue2 里对于对象上的新增属性无能为力。
而 Vue3 所使用的 Proxy,则是这样拦截的:
代码语言:javascript复制new Proxy(data, {
get(key) { },
set(key, value) { },
})
可以看到,根本不需要关心具体的 key,它去拦截的是 「修改 data 上的任意 key」 和 「读取 data 上的任意 key」。
所以,不管是已有的 key 还是新增的 key,都逃不过它的魔爪。
但是 Proxy 更加强大的地方还在于 Proxy 除了 get 和 set,还可以拦截更多的操作符。