Vue3源码-整体流程浅析

本文基于Vue 3.2.30版本源码进行分析
为了增加可读性,会对源码进行删减、调整顺序、改变部分分支条件的操作,文中所有源码均可视作为伪代码
由于ts版本代码携带参数过多,不利于展示,大部分伪代码会取编译后的js代码,而不是原来的ts代码

文章内容

本文仅针对有状态组件、同步逻辑进行分析,不分析异步相关逻辑和函数组件等内容,相关源码展示也会剔除异步相关逻辑和函数组件等内容
  • 流程图展示Vue3首次渲染整体流程
  • 以流程图为基础,进行首次渲染整体流程的源码分析
  • 流程图展示Vue3组件更新整体流程
  • 以流程图为基础,进行组件更新整体流程的源码分析
由于本文篇幅较长,按照流程图看源码分析效果更佳

创建vnode

render()函数

  • 使用createBaseVNode进行普通元素节点的vnode创建
  • 使用createVNode进行Component组件节点的vnode创建

内容最终会被编译成为render()函数,render()函数中执行createBaseVNode(_createElementVNode)/createVNode(_createVNode)返回渲染结果,在下面流程分析中

  1. 在一开始的app.mount会触发App.vuecreateVNode()函数获取vnode数据,进行patch()
  2. 在处理组件内部数据时,会触发renderComponentRoot()中调用render()函数(从而进行createBaseVNode/createVNode)获取组件内部的vnode数据,然后进行patch()
function render(_ctx, _cache) {
  with (_ctx) {
    const {createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, createVNode: _createVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock} = _Vue;
    const _component_InnerComponent = _resolveComponent("InnerComponent");
    const _component_second_component = _resolveComponent("second-component");
    return (
      _openBlock(),
      _createElementBlock("div", _hoisted_1, [
        _createElementVNode("div", _hoisted_2, [_hoisted_3, _hoisted_4, _hoisted_5, _createVNode(_component_InnerComponent)]),
        _createVNode(
          _component_second_component,
          {
            "stat-fat": {label: "3333"},
            test: "333",
          },
          null,
          8 /* PROPS */,
          ["stat-fat"]
        ),
      ])
    );
  }
}

首次渲染整体流程图

首次渲染整体流程-源码概要分析

const createApp = ((...args) => {
    const { createApp } = ensureRenderer(); // 第1部分
    const app = createApp(...args); // 第2部分
    //...
    const { mount } = app;
    app.mount = (containerOrSelector) => { // 第3部分
        //...
        const proxy = mount(container, false, container instanceof SVGElement);//第4部分
        //...
        return proxy;
    };
    return app;
});

第1部分: ensureRenderer() 创建渲染器,获取createApp

由下面的代码块可以知道,会将render()函数传入createAppAPI(render)进行createApp()方法的创建

function ensureRenderer() {
    return (renderer || (renderer = createRenderer(rendererOptions)));
}
function createRenderer(options) {
    return baseCreateRenderer(options);
}
function baseCreateRenderer(options, createHydrationFns) {
    //...初始化很多方法
    const render = (vnode, container, isSVG) => { };
    return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
    };
}

第2部分: 使用createApp()创建app对象

从下面的代码块可以知道,本质是将baseCreateRenderer()产生的render函数传入作为app.mount()中执行的一环,然后返回创建的app对象

function createAppAPI(render, hydrate) {
    return function createApp(rootComponent, rootProps = null) {
        const context = createAppContext();
        // ...
        let isMounted = false;
        const app = (context.app = {
            _uid: uid++,
            _component: rootComponent,
            _props: rootProps,
            _context: context,
            // ...省略
            mount(rootContainer, isHydrate, isSVG) {
              // ...省略
              vnode.appContext = context;
              render(vnode, rootContainer, isSVG);
              // ...省略
            }
            //...省略多种方法
        });
        return app;
    }
}

其中createAppContext()返回的一个初始化的对象,从下面的代码块可以看到全局变量app.config.globalProperties

export function createAppContext(): AppContext {
  return {
    app: null as any,
    config: {
      isNativeTag: NO,
      performance: false,
      globalProperties: {},
      optionMergeStrategies: {},
      errorHandler: undefined,
      warnHandler: undefined,
      compilerOptions: {}
    },
    mixins: [],
    components: {},
    directives: {},
    provides: Object.create(null),
    optionsCache: new WeakMap(),
    propsCache: new WeakMap(),
    emitsCache: new WeakMap()
  }
}

第3部分: 重写app.mount,暴露出来该方法,在外部显示调用

由于createApp重名两次,为了避免认错,将初始化示例代码再写一次

根据初始化示例代码,在createApp()中创建了app对象,并且进行了app.mount()方法的书写,从下面示例代码可以知道,最终会调用app.mount("#app")

import App from "./App.vue";
const app = createApp(App);
app.mount("#app");

const createApp = ((...args) => {
    const { createApp } = ensureRenderer(); // 第1部分
    const app = createApp(...args); // 第2部分
    //...
    const { mount } = app;
    app.mount = (containerOrSelector) => { // 第3部分
        //...
        const proxy = mount(container, false, container instanceof SVGElement);//第4部分
        //...
        return proxy;
    };
    return app;
});

重写app.mount("#app")详细源码分析

  • containerOrSelector可以为字符串/DOM,使用normalizeContainer()进行整理
  • 判断如果没有render函数template内容,则使用innerHTML作为组件模板内容
  • 挂载前清空innerHTML内容
  • 执行原始的mount()方法,返回执行后的结果
app.mount = (containerOrSelector) => { // 第3部分
    const container = normalizeContainer(containerOrSelector);
    if (!container)
        return;
    const component = app._component;
    if (!isFunction(component) && !component.render && !component.template) {
        //在 DOM 模板中可能会执行 JS 表达式
        component.template = container.innerHTML;
    }
    container.innerHTML = '';
    const proxy = mount(container, false, container instanceof SVGElement);//第4部分
    //...
    return proxy;
};

function normalizeContainer(container) {
    if (isString(container)) {
        const res = document.querySelector(container);
        return res;
    }
    return container;
}

第4部分: 触发原始的app.mount(dom)执行(重写app.mount执行过程中)

整体源码

// 重写app.mount: const createApp = ((...args) => {})里面逻辑
const { mount } = app; 
app.mount = (containerOrSelector) => { // 第3部分
    const proxy = mount(container, false, container instanceof SVGElement);//第4部分
    return proxy;
};
// 第4部分: 原始的app.mount:
function createAppAPI(render, hydrate) {
    return function createApp(rootComponent, rootProps = null) {
        const context = createAppContext();
        const app = (context.app = {
            mount(rootContainer, isHydrate, isSVG) {
                if (!isMounted) {
                    const vnode = createVNode(rootComponent, rootProps);
                    vnode.appContext = context;
                    render(vnode, rootContainer, isSVG);
                    isMounted = true;
                    app._container = rootContainer;
                    rootContainer.__vue_app__ = app;
                    return getExposeProxy(vnode.component) || vnode.component.proxy;
                }
            }
        });
        return app
    }
}

原始的app.mount的核心代码:render()(非