vue3源码分析

3,669 阅读19分钟

往期文章:

前言

期期盼盼,vue3终于来了,好用与否,见仁见智,不过目前而言,个人还是更倾向于使用 react

偏好 react 理由(仅为个人观点)

  • 1.react 有更加完善的hooks;vue3借鉴了react的hooks,但不完全
  • 2.Fiber调度机制,优秀的一匹~
  • 3.vue2 GC 不是很好,因为模版编译使用 with(ctx)、vue3已经修正过来,值得👍
  • 4.vue 数据流管理并不如 react 般严谨,有人会说,vue我也可以做的很严格,好吧,但实际项目开发中,为了实现某些需求(尤其接手别人的代码),你不得不写一些语法糖式代码,严重破坏的自上而下的数据流,项目质量增加了一定的风险,被坑过几次,已无力吐槽~
  • 5.vue 对 typescript 的支持不如 react,坑的一匹~,可以参考 Vue3-Vuex-TypeScript 踩坑之旅
  • 6.react 抽象公共组件、高阶组件相较于 vue 更加灵活、方便,JSX 相较于 template,更令话,便于控制
  • 7.vue 生态圈不如 react~
    • 组件库,不言而喻,目前 antd vue 支持 Vue 3.0 的 2.0.0 测试版 已发布,香不香,看疗效,最重要一点,antd vue 是由个人维护,antd react 是由大团队技术社区维护,尤其是公司的项目,可持续性是基础 ~
    • 无论是 recoil 还是 mobx,实际应用都优于 vuex

vuex 只能 vue 使用、因为 vuex 是 挂载在 vue上,监听数据是基于 新 new Vue 上

recoil 只能 react 使用,因为 recoil 基于react 中的 Provide、useEffect 基础上的,一共就四个API,非常简单有效,

mobx 谁都可以用,因为基于 observable 来实现的,并且 mobx 拥有自己数据流管理的功能,比如一个store,它可以自己管理自己的流程,比如 constructor中可以执行数据的一些基本化操作、when、autorun等钩子来监听数据,实际应用中,数据流完全可以在 mobx 中自行管理,外面只是引用和调用store方法,数据流管理,清晰可见,好不好,看疗效。

言归正传,还是的得学,那么咱们开始吧...

vue3 VS vue2

1.源码结构发生改变

  • vue2的所有方法都是挂载在 vue 实例上面,无论你使用与否,它们都会被打包到产出物中,监听数据是基于浏览器 Object.observer 能力,并且 observer API没有暴露出来

  • vue3 从代码结构发生改变,由 typescript 编写,每个模块可以作为一个单独的功能被外部使用,这样利于tree-shaking,并且 reactive等可以暴露给外部使用

packages 目录结构

  • reactivity 响应式数据处理
    • reactive ref effect 可以在不引用 vue 的情况下单独引用
  • runtime-core 与平台无关的 runtime(VNode、渲染器 vue组件实例...)
  • runtime-dom 针对于浏览器的 runtime
  • complier-core 与平台无关的编译器(render)
  • complier-dom 针对浏览器编译时
  • complier-sfc 编译处理单文件
    • 一个vue文件包含三部分:sfc将它们拆分出来分别处理,template => vue、script => js、style => css
  • complier-srr 服务端渲染编译器
  • server-renderer 服务端渲染
  • shared 内部实用函数和常量共享
  • size-check 私有包,用于检查 tree-shaking 后的运行时大小
  • template-explorer 在线模版编译工具
  • vue 主入口

2.模版编译

AST分析工具

vue2

  • template => AST => 遍历AST(查找静态节点并打上标记) => render => VNode => diff
    • 不区分动态还是静态模版,从上到下diff,开销很大
    • with 导致内存无法 GC,但 vue2 不得已用 with, 因为浏览器识别不了动态字符串 {{ greeting }} vue2 template-explorer
    • vue2 是在编译时进行分析优化,而react 使用 jsx => react.createElment 在运行时进行分析优化(fiber)
    • 模版编译比较耗性能,在 template 编译成 AST的过程中,是通过正则匹配进行实现的,而正则匹配的回夙性导致性能降低
例如:匹配/xf{1,3}fz/
xffz xfffz xffffz 不停的回溯直至匹配为止,即正则贪婪特性
  • optionsAPI 配置型API
new Vue({
  data: {},
  mounted: {},
  methods: {}
})

vue3

  • template => AST => 遍历AST => 生成新的AST=> render

    • vue3 做了静态编译优化:编译时做静态分析,把能提升的静态节点,提升到渲染函数外面,这样就不需要再创建一遍节点 例如:
<div>
    <section>
      <span> {{ name }}</span>
    </section>
</div>
--- 动态节点 <span>{{ name }}</span> ---

当 name 发生改变,不要从上至下递归进行 diff,只需要通过 dynamicChildren 进行定向 update,这大大提高了编译性能

1.block tree

const vnode = {
  tag: 'div',
  children: [
    {
      tag: 'section',
      children: [
        {
          tag: 'span',
          children: ctx.name,
          patchFlag: 1 // 动态的 textContent
        },
      ],
    },
  ],
  // 这个数组收集当前 VNode 下所有的动态子节点
  dynamicChildren: [
    { tag: 'span', children: ctx.name, patchFlag: 1 },
  ],
}

但有的情况不能采用定向更新模式,比如 v-if、v-for,它会重新创建一层 block

2.静态节点提升

3.静态属性的提升

4.事件缓存

5.静态节点超过6个,会对静态节点字符串化,这个NB了!!!

    • 模版编译时,基于状态机来遍历字符串
  • comppositionAPI: hooks型API
createApp({
  setup() {}
}).mount('#app')

3.响应式系统

vue2

  • 基于浏览器能力 Object.definePrototype,而它无法直接监听新增Key、深度嵌套(递归) 、数组(导致多次触发get/set)

vue3

  • 创建实例开销大,每次创建一个实例,需要在this上暴露很多东西,每个暴露的属性都需要在Object.defineProperty去定义,开销大,proxy把数据定义的过程丢掉,暴露的是proxy

    • Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,可以译为“代理器”
    • Proxy可以监听对象本身的变化,并在变化后执行相应的操作。可以实现追踪对象,同时在数据绑定方面也很有用处 var proxy = new Proxy(target, handler);

    new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为 注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象进行操作

  • 基于 proxy,可以监听新增key,无法直接通过get监听深嵌套和数组,需要额外处理

    • 数组,通过重写数组方法,拦截这些方法从而获得监听的能力
    • 对象深嵌套,采用只有获取某一个key时,才 reactive(懒代理),解决深度嵌套的性能问题
new Proxy(target, baseHandler);
const baseHandler = {
  get(target, key) {
    // 收集依赖
    track(target, key);
    const res = Reflect.get(target, key);
    return typeof res === 'object' ? reactive(res) : res;
  },
  set(target, key, val) {
    const res = Reflect.set(target, key, val);
    // 触发更新
    return res;
  },
  apply (target, ctx, args) {
    return Reflect.apply(...args);
  }
};

vue3 整体流程

import { createApp } from 'vue'
import App from './app'
const app = createApp(App)
app.mount('#app')

app 挂载流程

1.调用 createApp(App) 生成应用实例,然后调用mount方法

2.调用 mount 的阶段,首先根据入口组件 App 创建VNode,接着触发 render(reactive+effect) 函数,整个render的阶段,就是 Patch 所有 Vnode 的阶段,当中分为初始化挂载与更新组件 mount

3.对于内部 patch 的阶段,会对不同节点类型有不同的处理,主要是组件与element,组件就继续执行创建、挂载等等

data/props 挂到 ctx 上

代码分析:createApp => App

    • runtime-dom/src/index:createApp -> ensureRenderer -> createRenderer
export const createApp = ((...args) => {
  // 创建 app 实例对象
  const app = ensureRenderer().createApp(...args)
  // 重写mount方法(可以独立到不的平台实现
  const { mount } = app
  app.mount = (containerOrSelector) => {
    // ... 
    // 容器处理
    const proxy = mount(container)
    ontainer.removeAttribute('v-cloak')
    container.setAttribute('data-v-app', '')
    return proxy
  }
  return app
}) 

// 动态加载执行render,这样可以import  可以保证tree shake
function ensureRenderer() {
  return renderer || (renderer = createRendere(rendererOptions))
}
    • runtime-core/src/renderer: createRenderer -> baseCreateRenderer => { render, hydrate, createApp: createAppAPI(render, hydrate) }

node与element区别:所有的element元素都是node元素,继承node,node不一定是element,因为文本节点不是一个element,element是node的一种

 //创建的逻辑
export function createRenderer(options) {
  return baseCreateRenderer(options)
}

// 返回render渲染与createApp方法
function baseCreateRenderer(options,createHydrationFns) {
  const { dom操作方法... } = options
  /**
    patch方法,接受新老VNode节点与对应的容器,节点的兄弟节点,父组件,是否压缩优化等等,
    整个patch的过程其实就是对应的VNode深度优先遍历的过程;最终构造出一颗完整的树
  */
  const patch: PatchFn = (n1,n2,container,anchor = null,parentComponent = null,
  parentSuspense = null,isSVG = false,optimized = false) => {
    // 存在老的VNode,并且新的VNode类型与之不同,销毁对应的旧结点
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      //n1设置为null,保证后面走整个节点的mount逻辑
      n1 = null
    }

    //节点属于没有优化的类型
    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }

    /**
      patch内部有两种情况:
      1. 挂载 n1 为null
      2. 更新
    */
    const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text://处理文本
      case Comment://处理注释
      case Static://处理静态节点
      case Fragment://处理Fragment元素</>
      default: //elemment 处理DOM元素
        if (shapeFlag & ShapeFlags.ELEMENT) {
          // 元素处理逻辑
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          // 组件处理逻辑
          /**
            内部主要做了以下几件事情:
            STEP: 1.判断是挂载组件,还是更新组件,挂载调用mountComponent,更新调用updateComponent
            STEP: 2.对于首次挂载的吗mountedComponent内部,
                    1.创建组件实例,根据组件VNode
                    2.设置组件实例,调用setup方法,以及处理options等等、
                    3.触发并且运行带有副作用的渲染函数,内部会结合使用Effect与render的调用
            STEP: 3. 渲染真实逻辑
                      1. 生成对应的subTree,因为组件会是其他子组件的情况,这个过程是调用render的过程,然后生成对应的subTree
                      2. 把对应的subTree渲染到container中,通过执行对应的patch,然后再次进入到这个函数,如果patch是element,就开始触发渲染任务
          */
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          // 处理TELEPORT
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          // 处理SUSPENSE
        }
    }

    // set ref
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)
    }
  }
  
  // 处理文本
  const processText = (n1, n2, container, anchor) => {}
  // 处理注释
  const processCommentNode: ProcessTextOrCommentFn (n1,n2,container,anchor)
  // 处理静态
  const mountStaticNode = (n2,container,anchor,isSVG)
  // 处理Fragment元素</>
  const processFragment = (...) {}
  // 处理element: 1.首次初始化挂载 2.更新
  const processElement = (n1, n2) => {
   if (n1 == null) {
      // 初始化挂载节点element
      mountElement(n2,container,...)
    } else {
      // 更新节点的过程
      patchElement(n1, n2, ...)
    }
  }
  // 挂载element节点
  const mountElement = (vnode,container,...) => {
    // 新建 DOM 元素节点,直接调用options的对应平台方法
    el = vnode.el = hostCreateElement(vnode.type,isSVG,props && props.is)
    // 处理文本节点
    hostSetElementText(el, vnode.children as string);
    // 子节点是数组
    mountChildren(vnode.children,el,null,parentComponent,...)
    // 处理props的情况,class style event等等
    hostPatchProp(...)
    
    // 把创建的 element,插入到节点中
    hostInsert(el, container, anchor)
    // 
    queuePostRenderEffect(() => {
        vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
        needCallTransitionHooks && transition!.enter(el)
        dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
      }, parentSuspense)
  }
  
  // 当子节点是数组的时候,需要遍历处理patch
  const mountChildren = (children,container,anchor,...) => {
    patch(....)
  }
  // 更新element节点,主要是更新props和子节点,内部还涉及到动态patch更新
  const patchElement()
  // 通过动态black tree触发的修改
  const patchBlockChildren = (...) => {}
  // 更新一个节点的props,可能是class style、event等等
  const patchProps = (...) => {}
  
  // 组件处理逻辑,判断子组件是否需要更新,如果是则递归执行子组件的副作用渲染函数,否则直接更新VNode
  const processComponent() {
    // 组件初始化挂载
    if (是否需要更新) {
      mountComponent(...)
    } else {
      // 更新
      updateComponent(...)
    }
  }
  // 挂载组件
  const mountComponent = (...) {
    // 1.创建组件实例
    const instance = (initialVNode.component = createComponentInstance(
      initialVNode,
      parentComponent,
      parentSuspense
    ))
    // 2.设置组件实例
    setupComponent(instance)
    // 3.设置并且运行带有副作用的渲染函数
    setupRenderEffect(instance,...) 
  }
  // 更新组件:dom diff更新dom来触发
  const updateComponent = (n1: VNode, n2: VNode,...) => {
    const instance = (n2.component = n1.component)!
    // 根据新旧子组件VNode判断是否需要跟新子组件
    if (shouldUpdateComponent(n1, n2, optimized)) {
      // 更新对应组件实例的next为新的VNode
      //调用子组件的渲染函数
      instance.update()
    } else {
      // 不需要更新,只需要复制新老组件的属性
    }
  }
  
  // 运行带有副作用的render函数
  const setupRenderEffect = (instance,...) => {
    // 创建响应式的副作用渲染函数,当组件内部数据发生变化,会重新执行componentEffect方法内部逻辑,并且挂载到组件实例的update方法上,后续更新执行
    instance.update = effect(function componentEffect() {
      if (!instance.isMounted) {
        // 第一次组件挂载,初始化渲染
        // 1.生成对应的subTree
        // 2.将对应的subTree渲染到container中
        
        // 渲染组件生成子树VNode,
        const subTree = (instance.subTree = renderComponentRoot(instance))
      } else {
        // 把对应的subTree渲染到container中,子树可能是element、text、component等
        path(null,subTree,container,...)
      }
    })
  }
  
  // 更新组件实例的对应信息,比如组件的props、Vnode指向,同时更新VNode节点指向的组件实例
  const updateComponentPreRender = (instance,nextVNode,...) => {}
  /** 更新子节点:
  1.如果旧结点是文本
    1.新节点是文本,直接替换
    2.新节点是空,直接删除
    3.新节点是数组,清空文本,添加数组
  2.如果旧结点为空
    1.新节点是文本,添加文本
    2.新节点是空,不做处理
    3.新节点是数组,添加多个节点
  3.如果旧结点是数组
    1.新节点是文本,删除节点,添加文本
    2.新节点是空,删除旧节点
    3.新节点是数组,需要完整diff子节点
  */
  const patchChildren: PatchChildrenFn = () => {}
  // diff,数组子节点发生变更,主要是,更新、删除、添加、移动几种方式处理
  const patchKeyedChildren = () => {}
  
  // 渲染和挂载的流程
  const render = (vnode, container) => {
    // 卸载
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      // 创建或者更新组件
      patch(container._vnode || null, vnode, container)
    }
    flushPostFlushCbs();
    // 缓存VNode节点(已经渲染过)
    container._vnode = vnode
  }
  
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}

-- runtime-core/src/apiCreateApp: createAppAPI

export function createAppAPI(render,hydrate) {
  // 接受两个参数,根组件的对象与props,默认为null
  return function createApp(rootComponent, rootProps = null) {
    // 创建应用context
    const context = createAppContext();
    /**
      ctx = { app, config: {
        isNativeTag: NO,
        performance: false,
        globalProperties: {},
        optionMergeStrategies: {},
        isCustomElement: NO,
        errorHandler: undefined,
        warnHandler: undefined
      },
      mixins: [],
      components: {},
      directives: {},
      provides: Object.create(null)
    }
    */
    // 所有插件
    const installedPlugins = new Set();
    let isMounted = false
    
    // app实例
    const app: App = (context.app = {
      _uid: uid++,
      _component: rootComponent,
      _props: rootProps,//props
      _container: null,//挂载容器
      _context: context,
      version,
      get config() {
        return context.config
      },
      // 通过 use 挂载中间件,
      use(plugin, ...options) {
        installedPlugins.add(plugin)
        plugin.install(app, ...options)
        return app;
      },
      // 用mixin
      mixin(mixin) {
        context.mixins.push(mixin)
        return app
      },

      // 在 app 实例上定义组件
      component(name, component) {
        if (!component) {
          return context.components[name]
        }
        context.components[name] = component
        return app
      },
      //定义指令
      directive(name, directive) {
        if (!directive) {
          return context.directives[name]
        }
        context.directives[name] = directive
        return app
      },


      // 挂载,组件核心渲染逻辑
      mount(rootContainer, isHydrate) {
        if (!isMounted) {
          // 没有挂载,创建根节点的VNode
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          );
          // 绑定上下文
          vnode.appContext = context

          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            // 实例化触发render,使用渲染器渲染VNode,传入VNode与container容器
            render(vnode, rootContainer)
          }
          
          isMounted = true
          
          app._container = rootContainer

          return vnode.component!.proxy
        }
      },
      //卸载
      unmount() {
        if (isMounted) {
          render(null, app._container)
        }
      }
      provide(key, value) {
        context.provides[key as string] = value
        return app
      }
  }
}

数据响应式分析

  • reactive: 把复杂(object、array等)类型数据并返回响应式数据proxy
  • ref: 把基本(number、string等)类型数据并返回响应式数据proxy
  • effect(fn):监听数据变化处理函数

当初始化时,执行fn,收集依赖 => 渲染页面

过程:render => VNode => patch => mount

当fn中的响应式数据发生变化时,effect 重新执行 fn => 更新 => 渲染页面

过程:render => VNode => patch => diff => update,例如

const state = reactive({ count: 1 })
effect(() => {
  const count = state.count;
  console.log(count);
})

reactive

  • get: 收集依赖 track
  • set: 触发更新 trigger
// 将原始数据转化成响应数据
export const proxyMap = new WeakMap<Target, any>()/
export function reactive(target: object) {
  // 只读数据,直接返回
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  // 创建响应式数据
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

function createReactiveObject(target, isReadonly, mutableHandlers, mutableCollectionHandlers) {
  // 如果不是 Object,直接返回target,也就说明,你要监听的数据传值必须是 {}
  // ...
  // 如果已经是响应式数据,并且不是只读的,直接返回
  // ...
  // 如果原始数据已经有代理数据,从 proxyMap中找到相应代理后的数据
  const existingProxy = proxyMap.get(target)
  if (existingProxy) { return existingProxy }
  // 没找到,就处理代理数据
  const proxy = new Proxy(target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers)
  // 将 proxy 插入 proxyMap 中留存
  proxyMap.set(target, proxy);
  return proxy
}
// ------- handler --------
// mutableHandlers
export const base: ProxyHandler<object> = {
  get:createGetter,// 收集依赖
  set, // 发布订阅,触发更新
  deleteProperty,
  has,
  ownKeys
}
function createGetter() {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 如果是数组,则需要特殊处理,因为数组变化可能导致多次set、get触发
    const targetIsArray = isArray(target);
    // 数组调用内置方法,而非原型链上的方法
    if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    
    // 其他类型
    const res = Reflect.get(target, key, receiver)
    
    // 如果是对象 
    /**
     data: {
       bar: {
         foo: 1
       },
       car: {
         price: 100
       }
     }
     1.当执行 data.bar 时,只会将 bar 转化为 reactive
     2.当执行 data.bar.foo 时,才会将 foo 转化 reactive
    */
    if (isObject(res)) {
      // 如果是对象,reactive,说白了就是递归 reactive,但并不是一开始就递归所有key => reactive,只有当获取某一个key时,才 reactive
      return isReadonly ? readonly(res) : reactive(res)
    }
    
    // 不是只读数据,开始收集依赖
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key) 
    }
    
    return res;
  }
}

// 重写数组,直接重写方法,规避多次触发 get/set 问题
const arrayInstrumentations: Record<string, Function> = {}
// 重写获取数据方法
(['includes', 'indexOf', 'lastIndexOf'] as const).forEach((key) => {
  // 获取 数组 原本的方法 
  const method = Array.prototype[key] as any
  arrayInstrumentations[key] = function (this: unknown[], ...args: unknown[]) {
	// 获取代理数据的原始数据
    const arr = toRaw(this)
    // 收集依赖
    for (let i = 0, l = this.length; i < l; i++) {
      track(arr, ‘get’, i + '') 
    }
    // 第一次执行,可能参数是响应式数据的情况
    const res = method.apply(arr, args) 
    if (res === -1 || res === false) {
      // 数据被代理的情况,查找原始数据,重新处理
      return method.apply(arr, args.map(toRaw))
    } else {
      return res
    }
})
(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach((key) => {
 // ....
}

function createSetter(shallow = false) {
  return function set(target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    // 获取到原来的值
    const oldValue = (target as any)[key]
    if (!shallow) {
      // 处理对应 ref 数据的情况
      value = toRaw(value)
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        // ref的时候,需要修改value来触发对应数据的内部方法
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    // 判断这个 key 是新添加的,还是修改的
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    // 触发修改逻辑
    const result = Reflect.set(target, key, value, receiver)

    // receiver 为 Proxy 或者继承 Proxy 的对象,
    // 这里需要处理原型链的情况,因为如果原型链继承的也是一个proxy,通过Reflect.set修改原型链上的属性会触发两次setter
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        //trigger 派发通知
        trigger(target, 'add', key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, 'set', key, value, oldValue)
      }
    }
    return result
  }
}

effect

  • effect(() => state.name) 过程
    • 初始化执行,fn => 包装 createReactiveEffect(fn) => activeEffect
    • fn() => 触发get => track收集依赖
/**
 * effect 包装依赖函数的逻辑,返回 activeEffect
 * fn 具体要执行的函数
 * options 配置项
 */
export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  // 如果fn已经是一个被 effect 包装过的函数,那么就直接指向原始函数
  if (isEffect(fn)) {
    fn = fn.raw
  }
  /**
   * 创建一个包装逻辑
   * 因为需要在数据获取的时候,收集依赖,那就应该在执行之前,把处理逻辑赋值给reactiveEffect
   * 当 effect 内部函数执行的时,内部获取数据的逻辑,就可以直接添加依赖
   */
  const effect = createReactiveEffect(fn, options)
  //判断是否需要lazy,如果不是lazy,直接执行一次
  if (!options.lazy) {
    effect()
  }
  //返回包装后的函数
  return effect
}

// effect栈
const effectStack: ReactiveEffect[] = [

// 包装函数
function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    // 在 effect 没有被激活并且没有调度选项,则直接执行fn
    if (!effect.active) {
      return options.scheduler ? undefined : fn()
    }
    // 在初始化执行时,effectStack 会先收集 effect,再执行 fn
    // 因为 在执行 fn 的过程中,修改对应数据会触发执行 effect,通过 effectStack 可以避免这个问题
    if (!effectStack.includes(effect)) {
      // console.log(effect.deps, '当前数据执行过程中的依赖')
      // 清空 effect.dep 中所依赖的 effect,避免多次渲染,
      
      /** 为什么要清空 effect.dep 依赖
        const state = reactive({
          show:true,
          name:'xfz',
          default: 'girl'
        });
        // 监听 开始执行,依赖收集,收集了show与name的字段,映射到effect上,
        effect(function(){
          if(state.show){
            console.log(state.name)
          } else {
            console.log(state.default)
          }
        })
        
        // 修改 show
        state.show = false  // 输出 girl
        // 此时如果不清空dep里面的所有依赖,修改 name
        state.name = 'zc' // 会再次输出 girl,其实不应该再次执行fn,因为这样会造成多次渲染
      */
      
      cleanup(effect)

      try {
        // 开启允许依赖收集
        enableTracking()
        // effect 入栈
        effectStack.push(effect)
        // 设置激活的 effect
        activeEffect = effect
        // 执行fn逻辑,触发内部数据依赖的收集,开始收集 activeEffect
        return fn(); // fn() => 触发get => track收集依赖 
      } finally {
        // 出栈
        effectStack.pop()
        // 恢复之前的状态
        resetTracking()
        // 指向栈最后一个effect
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  effect.id = uid++
  effect.allowRecurse = !!options.allowRecurse
  effect._isEffect = true // 表示是一个 effect 函数
  effect.active = true // 激活状态
  effect.raw = fn // 原始的函数
  effect.deps = [] // effect对应的依赖 ,一个effect里面,可能存在多个监听数据
  effect.options = options
  return effect
}

// 清除对应的依赖执行
function cleanup(effect: ReactiveEffect) {
  const { deps } = effect; // 一个effect可能有多个数据依赖
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}

let shouldTrack = true // 是否应该收集依赖
const trackStack: boolean[] = [] // 控制收集依赖的多次嵌套的状态

// 暂停依赖的收集
export function pauseTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = false
}
// 开始依赖的收集
export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}
// 回退到上一次依赖的收集
export function resetTracking() {
  const last = trackStack.pop()
  shouldTrack = last === undefined ? true : last
}

track 收集依赖

// { [’target1‘]:  depsMap }
const targetMap = new WeakMap<any, KeyToDepMap>();

/**
 * 收集依赖
 * target 原始数据
 * type 触发的类型,如 get、add
 * key 获取数据具体的某一个key值
 */
export function track(target: object, type: TrackOpTypes, key: unknown) {
  // activeEffect 数据发生变化之后,需要做的事情
  // 当前依赖不应该收集,或者没有对应激活的effect,直接返回,不收集
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  /*
  {
    target<原始数据>:{
      key<对应的key>:[effect]<当前key数据,所有关联的监听使用的地方,一个key,可能多个地方使用,就会存在一个key对应对个effect>
    }
  }
  */
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    // 每一个target,对应一个depsMap
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    // depsMap中维护了对应的key到dep的集合
    depsMap.set(key, (dep = new Set()))
  }
  if (!dep.has(activeEffect)) {
    // 收集当前激活的effect作为依赖
    dep.add(activeEffect)
    // 当前激活的effect,收集dep作为依赖,当前effect内部有可能触发其他effect
    activeEffect.deps.push(dep) 
    if (__DEV__ && activeEffect.options.onTrack) {
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key
      })
    }
  }
}

trigger 触发更新,派发通知

export function trigger(target, type, key, newValue, oldValue, oldTarget) {
  // 获取到对应数据原始数据的依赖集合
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // 没有被收集,直接返回
    return
  }
  // 创建需要运行的effect集合
  const effects = new Set<ReactiveEffect>()
  // 定义遍历添加effect的函数
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        if (effect !== activeEffect || effect.allowRecurse) {
          effects.add(effect)
        }
      })
    }
  }
  //触发了对应的操作,修改、删除、添加,都添加effect依赖到effects中
  if (type === TriggerOpTypes.CLEAR) {
    depsMap.forEach(add) 
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      add(depsMap.get(key))
    }

    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          add(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          add(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }
  // 定义执行函数
  const run = (effect: ReactiveEffect) => {
    if (__DEV__ && effect.options.onTrigger) {
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }

    // 如果有调度函数,就先执行调度函数
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      //没有的话,直接执行
      effect()
    }
  }

  effects.forEach(run);//遍历执行effect
}

ref

// 创建ref数据,处理基本类型数据的监听
export function ref(value?: unknown) { 
  return createRef(value)
}
function createRef(rawValue: unknown, shallow = false) {
  //如果是ref,直接返回
  if (isRef(rawValue)) {
    return rawValue
  }
  // 创建ref
  return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
  constructor(private _rawValue: T, public readonly _shallow = false) {
    this._value = _shallow ? _rawValue : convert(_rawValue) //处理数据的转换,如果是复杂数据,修改为Reactive的情况
  }
  //通过设置.value的情况,来触发整个track的过程
  get value() {
    //收集依赖
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newVal) {
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      // 派发通知
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}

模版编译

compile -> parse -> AST -> render

export function compile(template, options {
  return baseCompile(template, extend({}, parserOptions, options, {...})
}
export function baseCompile(template,options) {
  // 通过baseParse编译生成AST
  const ast = isString(template) ? baseParse(template, options) : template
  // 转换对应的AST
  transform(ast,extend({}, options, {...}));
  return ast;
}

调度

  • 维护一个promise队列,通过 push 加入任务,flush执行任务
const queue: SchedulerJob[] = []

export function nextTick(
  this: ComponentPublicInstance | void,
  fn?: () => void
): Promise<void> {
  const p = currentFlushPromise || resolvedPromise
  return fn ? p.then(this ? fn.bind(this) : fn) : p
}
---------------------------------------------
export function queueJob(job: SchedulerJob) {
  if (
    (!queue.length ||
      !queue.includes(
        job,
        isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
      )) &&
    job !== currentPreFlushParentJob
  ) {
    queue.push(job)
    queueFlush()
  }
}
function queueFlush() {
  if (!isFlushing && !isFlushPending) {
    isFlushPending = true
    currentFlushPromise = resolvedPromise.then(flushJobs)
  }
}

尾声

感觉好累,感觉代码好乱,不想再看了,以后希望不要用这玩意,我还是研究 react 去把~~

后续文章

❤️ 加入我们

字节跳动 · 幸福里团队

Nice Leader:高级技术专家、掘金知名专栏作者、Flutter中文网社区创办者、Flutter中文社区开源项目发起人、Github社区知名开发者,是dio、fly、dsBridge等多个知名开源项目作者

期待您的加入,一起用技术改变生活!!!

招聘链接: job.toutiao.com/s/JHjRX8B