简要理解Vue3响应式
0. 前言
vue2 即将结束 LTS,一个时代结束。
vue2 到 vue3 的转变有很多,组合式 api,新的响应式,vite,pinia 等。今天简单学习了一下 vue3 的响应式,记录一点简单理解。
1. Object.defineProperty 和 Proxy
vue2 使用的 Object.defineProperty 去劫持对象的 getter 和 setter,通过订阅发布模式以实现前端的响应式更新。
vue3 使用最新的 API Proxy,劫持对象的 getter 和 setter,通过订阅发布实现前端的响应式。
为什么要做了这个替换呢,要知道做了这个替换可是下了很大决心的,Proxy 这个新的 API 是没有 Polyfill 的,也就是说 Vue3 将完全无法支持 IE 等不支持 Proxy 的浏览器。
原因主要有:
- Object.defineProperty 对于深层对象只能循环递归,性能上比 Proxy 低。
- Object.defineProperty 无法监听数组的增删,vue2 之所以能够对一些常用操作做监听例如 push,pop,是因为 vue2 重写了数组方法以触发响应式,对于无法触发响应式的操作,文档也提及了 Vue.$set 方法
- Object.defineProperty 无法监听对象的增删。vue2 中也是只能使用 Vue.$set 方法。
以上,vue3 决心放弃 Object.defineProperty 转为使用 Proxy
2.Proxy 如何实现劫持
1 | /** |
引用一段抄来的代码,Proxy 接受一个对象,通过劫持对象的 get 操作和 set 操作,通过反射 Reflect 作用到劫持的对象上。在 get 和 set 中可以实现订阅发布。
3. Reactive VS Ref
刚学 vue3 的时候,会有很多疑惑,在 vue2 里面 this.data.a 直接赋值就行了,为什么 vue3 里面有些需要带.value,有些不需要带.value,有些对象可以直接赋值,有些对象要带.value 赋值等。
我的理解是:
牢记:Proxy 代理的永远是【对象】
3.1 Reactive
首先讲一下 reactive,reactive 的功能其实就是上述伪代码的功能,将一个对象转化为响应式对象,类似于
1 | // 伪代码,非具体实现 |
这样子,我们在使用 myReactiveObj.a
的时候,就进入到 Proxy 的劫持了,Proxy 将从劫持的对象中取出数据 1。
3.2 Ref
那为什么又存在 ref 呢?许多教程提到过,ref 是用来将原始值(String / Number / BigInt / Boolean / Symbol / Null / Undefined)转为响应式对象的。为什么是原始值呢?
上文说到过 Proxy 代理的永远是对象,那么原始值我们怎么监听呢?
ref 的做法是,把他放到一个对象里面去就好了。
类似于把传入 ref 的变量变为
1 | // 伪代码,非具体实现 |
这样子,其实我们劫持的是对象{ value: 1 }
,所以我们在使用的时候,并不是使用 myRef,myRef 是一个劫持对象。所以需要使用 myRef.value 才能取到我们想要的数据 1。
转为对象的时候还有一个优势,就是将 ref 作为函数参数传参的时候,使用的是引用传值,可以将响应式传递下去
4. 响应式丢失
响应式丢失是什么问题?
1 | const obj = { |
为什么会出现这种情况呢?关键还是上面说的
牢记:Proxy 代理的永远是【对象】
const { a, b } = myReactiveObj
这一步中,我们对 myReactiveObj 对象中的 a 属性进行了 get 调用,然后把调用的返回值赋予给了新的变量 a。这个返回值,已经是原始值 1(Number)了,对一个原始值再怎么赋值,也跟响应式无关了,因为 Proxy 劫持的是对象,劫持的是obj.a
的操作和obj.a += 1
的操作。一切都要在劫持对象 obj 的框架下进行。解构之后就与 obj 无关了。
那么要如何保持响应式的同时使用解构呢?答案是使用 toRefs。
1 | const obj = { |
toRefs 使用 ref 对传入的对象的每个 key 进行包裹,建立一个ref(myReactiveObj.a).value
和myReactiveObj.a
的响应式传递,即
1 | const obj = { |
5. 小测
综上,我们可以轻易得出一下问题的结论
1 | // 无效 |
这段代码响应式无效是因为,x 原本为 Proxy 对象,直接替换 Proxy 对象,无法触发对象属性的劫持,不会触发响应式。
1 | // 有效 |
这段代码有效是因为,x 是 Proxy 对象,对.value 的赋值操作会触发响应式更新。