前言
Vue3.0 和 Vue2.0 整体的响应式思路没有变化,但是实现细节发生了较大的变化。并且 Vue3.0 将响应式系统进行了解耦,从主体代码中抽离了出来,这意味着,我们可以将 Vue3.0 的响应式系统视作一个单独的库来使用,就像 RxJS。为了加深对 Vue3.0 的响应式原理的了解所以有了此篇文章。
设计思路
虽然 Vue3.0 和 Vue2.0 的响应式思路没有变,但为了方便回顾和讲解,我们还是重新理一下 Vue 的响应式系统设计思路
什么是响应式
对象 A 发生变化后,对象 B 也随之发生改变
- 响应式编程:b 变量 或者 c 变量的值 发生变化,a 变量的值 也随之发生改变(a := b + c)
- 响应式布局:视图窗口 发生变化,视图内元素布局 也随之发生改变
- MVVM:Model 发生变化,View 也随之发生变化
在 Vue 里的 对象 A 就是 数据,对象 B 则是 视图的渲染函数 或者 watch 或者 computed
Vue 的响应式系统需要做到什么
- 如何知道 数据 发生了变化
- 如何知道 响应对象 依赖了哪些 数据,建立依赖关系
- 如何在 数据 发生改变后通知依赖的 响应对象 做出响应
我们再将上面的需求转换成我们或多或少都听过的专业术语
- 数据挟持
- 依赖收集
- 派发更新
实现原理
数据挟持
概述
如何知道数据发生了变化呢,这问题再进一步就是 如何知道数据被操作了呢,其中的操作包括(增、删、改、查)。
Vue2.0 是利用 Object.defineProperty 可以挟持并自定义对象的 setter 操作和 getter 操作来实现的,但是这有一些问题:
- 数组 这一数据类型的数据 defineProperty 无法直接挟持,需要使用比较 hack 的操作去完成
- 增、删 操作在一些场景下无法捕获到,在无法挟持到的场景下,我们就必须使用
$set
、$delete
这些 Vue 封装的函数去代替 js 原生的值操作,增加了心智成本 - 性能的浪费,因为无法知道具体哪些值是需要响应式的,Vue2.0 会不管三七二十一,只要是在 data 里能挟持的都会挟持一边,但实际上开发者数据更改的粒度往往不会这么细,所以这会导致一定程度上的性能浪费(用
Object.freeze()
等操作可以在一定程度上解决这个问题)
Vue3.0 则是使用 Proxy 来实现数据更改的监听,Proxy 从定义上来说简直就是完美的为 **数据挟持 **这一目的而准备的,请看 MDN 介绍:
从某种意义上来讲,Proxy 可以视为 Object.defineProperty 的强化,它拥有更丰富的可以挟持的内容,并且解决了上面描述使用 defineProperty 存在的问题:
- 不再需要专门为 数组 进行特殊的挟持操作,Proxy 直接搞定
- 增、删 操作 Proxy 也能直接挟持
- 因为 Proxy 的一些特点,Proxy 可以实现惰性的挟持,而不需要深度的挟持所有值(后面再说怎么实现的)
- 同时因为 Proxy 并不是对数据源进行更改,从而可以保证不会出现太多的副作用
实现细节
Vue3.0 对于数据的响应式挟持,统一使用 composition API 来实现,我们仅对最典型的 reactive()
来讲解一下
reactive
createReactiveObject
mutableHandlers
Object/Array 类型的代理处理器
collectionHandlers
Map/Set/WeakMap/WeakSet 类型的代理处理器
由于上述四个类型修改值都是通过函数修改的,所以代理函数只拦截 get 方法,用于拦截响应对象调用了哪个操作函数,再进行具体的依赖收集或者派发更新
依赖收集
概述
如何知道 响应对象 依赖了哪些 数据,这个问题进一步就是 响应对象用了哪些数据。
Vue 的大体思路是这样的,比如我一个函数 fnA,里使用了 data 里的 B 和 C。
想要知道 fnA 使用了 B 和 C,那我们干脆就直接运行一下 fnA,在 B 和 C 里面等待 fnA 的获取,然后建立两者的依赖。
Vue2.0 里有一些概念 Watcher,Dep,target。
- Watcher 就是指 fnA
- Dep 则是存在 B 的 setter 里面的一个对象,用于存放 Watcher 集合
- target 则是现在正在进行依赖收集的 Watcher
简单粗暴来讲
new Watcher(fnA) => **target 等于当前 Watcher并调用 fnA => fnA 获取 B 的值,B 会将 target 放到自己的 Dep 上 => B 更新了,通知自己 Dep 上的 Watcher 重新执行 fnA
Vue3.0 思路差不多,但是实现上大有不同,因为 Vue3.0 不再随意对数据进行侵入式修改或者挟持,所以 Vue3.0 单独拎出来了一个静态变量存储依赖关系,这个变量叫做 targetMap
同时引进了一个新概念 effect,它与 Vue2.0 的 **Watcher **差不多,但是概念有些转换,从 监听者 变成了 副作用,指的是 对应依赖 值改变后会发生的副作用
数据类型
数据类型大概如下:
targetMap 的 key 指向的是 A 和 B 所在的对象 Data,value 指向的是
KeyToDepMap 的 key 的是 'A' 和 'B' 的键值,value 则是存放 effect 里面的 Watcher 集合
实现细节
Effect
effect 实际上就是 Vue2.0 里面的 Watcher,只不过做的事情相对而言化繁为简了
createReactiveEffect
reactiveEffect
track
会在数据挟持的 get
/ has
/ ownKeys
中调用
派发更新
概述
派发更新可以说是三个大环节中最简单的一部分了,但 Vue3.0 对比 Vue2.0 实现了更多细节,只需要在 **值发生变动的时候从依赖关系中取出对应的副作用集合,触发副作用 **即可。
我们可以在上面数据挟持中的 set
/ deleteProperty
发现派发更新的函数 trigger
的调用。
实现细节
trigger
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!