Vue3.0数据响应式原理详解
基于Vue3.0发布在GitHub上的第一版源码(2019.10.05)整理
预备知识
- ES6 Proxy,整个响应式系统的基础。
- 新的composition-API的基本使用,目前还没有中文文档,可以先通过这个仓库(composition-api-rfc)了解,里面也有对应的在线文档。
先把Vue3.0跑起来
先把vue-next仓库的代码clone下来,安装依赖然后构建一下,vue的package下的dist目录下找到构建的脚本,引入脚本即可。
下面一个简单计数器的DEMO:
<!DOCTYPE html> <html lang="en"> <body> <div id='app'></div> </body> <script src="/UploadFiles/2021-04-02/vue.global.js">template和之前一样,同样Vue3也支持手写render的写法,template和render同时存在的情况,优先render。
setup选项是新增的主要变动,顾名思义,setup函数会在组件挂载前(beforeCreate和created生命周期之间)运行一次,类似组件初始化的作用,setup需要返回一个对象或者函数。返回对象会被赋值给组件实例的renderContext,在组件的模板作用域可以被访问到,类似data的返回值。返回函数会被当做是组件的render。具体可以细看文档。
reactive的作用是将对象包装成响应式对象,通过Proxy代理后的对象。
上面的计数器的例子,在组件的setup函数中,创建了一个响应式对象state包含一个count属性。然后创建了一个increment递增的函数,最后将state和increment返回给作用域,这样template里的button按钮就能访问到increment函数绑定到点击的回调,count也能显示在按钮上。我们点击按钮,按钮上的数值就能跟着递增。
下面切入正题,我们就来探究下按钮上count值跟着响应式更新的原理
数据结构
首先列一下主要的一些数据结构,先列在这里,后面提到可以翻回来看看。
ReactiveEffect 一个Function对象,用于执行组件的挂载和更新。
interface ReactiveEffect { (): any isEffect: true active: boolean raw: Function // 具体执行的函数 deps: Array<Dep> computed"htmlcode">export type Dep = Set<ReactiveEffect> export type KeyToDepMap = Map<string | symbol, Dep> export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()Proxy代理拦截
reactive函数执行,会将传入的target对象通过Proxy包装,拦截它的get,set等,并将代理的target缓存到targetMap,targetMap.set(target, new Map())。
代理的get的时候会调用一个track函数,而set会调用一个triger函数。分别对应依赖收集和触发更新。
上一篇:浅谈vue项目用到的mock数据接口的两种方式// Proxy get 简化 function get(target: any, key: string | symbol, receiver: any) { // 通过key拿到原始值res const res = Reflect.get(target, key, receiver) // 过滤不需要代理的情况 // ... // 依赖收集 track(target, OperationTypes.GET, key) // 如果取到的值是个对象,将对象再代理包装一下 // Proxy只能代理对象第一层级 return isObject(res) "color: #ff0000">依赖收集和触发更新
组件在render阶段,视图会读取数据对象上的值进行渲染,此时便触发了Proxy的get,由此触发对应的track函数,记录下了对应的ReactiveEffect,也就是常说的依赖收集。
ReactiveEffect其实就可以看作是组件的更新(mount是特殊的update),数据的变更触发trigger,trigger遍历调用track收集的对应的数据的ReactiveEffect,也就是对应有关联的组件的更新。
trigger触发的组件的更新,在render阶段又触发了新一轮的track依赖收集,更新依赖。
下一篇:Vue分页插件的前后端配置与使用