当前位置 主页 > 网站技术 > 代码类 >

    Vue3.0数据响应式原理详解

    栏目:代码类 时间:2019-10-19 21:04

    基于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="./dist/vue.global.js"></script>
    <script>
    const { createApp, reactive, computed } = Vue;
    
    const RootComponent = {
     template: `
      <button @click="increment">
       Count is: {{ state.count }}
      </button>
     `,
     setup() {
      const state = reactive({
       count: 0,
      })
    
      function increment() {
       state.count++
      }
    
      return {
       state,
       increment
      }
     }
    }
    
    createApp().mount(RootComponent, '#app')
    </script>
    </html>
    
    

    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?: boolean
     scheduler?: (run: Function) => void
     onTrack?: (event: DebuggerEvent) => void
     onTrigger?: (event: DebuggerEvent) => void
     onStop?: () => void
    }
    

    targetMap 类似 {target -> key -> dep}的一个Map结构,用于缓存所有响应式对象和依赖收集。

    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函数。分别对应依赖收集和触发更新。