本文次要依据vue3源码去了解分明vue3的组件挂载流程(最初附流程图),依据集体浏览源码去解释,vue的组件是怎么从.vue单文件组件一步步插入到实在DOM中,并渲染到页面上。

例子阐明

那上面的简略代码去做个例子阐明,先看看vue3的写法。

  • App.vue

    <template>  <h1>Hello <span class="blue">{{ name }}</span></h1></template><script>import { defineComponent } from 'vue';export default defineComponent({  setup() {    return {      name: 'world'    }  }})</script><style scoped>.blue {  color: blue;}</style>
  • index.js

    import { createApp } from "vue";import App from "./App.vue";const root = document.getElementById('app');console.log(App);createApp(App).mount(root);
  • index.html

    <body>  <div id="app"></div>  <script src="/bundle.js"></script> <-- index.js通过打包 变成 bundle.js是通过打包的js --></body>

通过这三个文件vue3就能够把App.vue中的内容渲染到页面上,咱们看看渲染后果,如下图:

看下面的例子:咱们能够晓得,通过vue中的createApp(App)办法传入App组件,而后调用mount(root)办法去挂载到root上。到这里就会有些疑难❓

  • App组件通过vue编译后是什么样子的?
  • createApp(App)这个函数外面通过怎么样的解决而后返回mount办法?
  • mount办法是怎么把App组件挂载到root上的?
  • ...

先看看第一个问题,咱们下面代码有打印console.log(App),具体看看App通过编译后是失去如下的一个对象:

其中setup就是咱们组件定义的setup函数,而render的函数代码如下,

const _hoisted_1 = /*#__PURE__*/createTextVNode("Hello "); // 动态代码晋升const _hoisted_2 = { class: "blue" }; // 动态代码晋升const render = /*#__PURE__*/_withId((_ctx, _cache, $props, $setup, $data, $options) => {  return (openBlock(), createBlock("h1", null, [    _hoisted_1,    createVNode("span", _hoisted_2, toDisplayString(_ctx.name), 1 /* TEXT */)  ]))});

到这里就不难看出,App.vue文件通过编译之后失去一个render函数,详情请看在线编译。当然还有css代码,编译后代码如下:

var css_248z = "\n.blue[data-v-7ba5bd90] {\r\n  color: blue;\n}\r\n";styleInject(css_248z);

styleInject办法作用就是创立一个style标签,style标签的内容就是css的内容,而后插入到head标签中,简略代码如下,

function styleInject(css, ref) {  var head = document.head || document.getElementsByTagName('head')[0];  var style = document.createElement('style');  style.type = 'text/css';  style.appendChild(document.createTextNode(css));    head.appendChild(style);}

渲染流程

从下面的例子咱们能够看出,其实vue就是把单文件组件,通过编译,而后挂载到页面上,css款式插入到head中,其大抵流程图如下:

第一局部的.vue文件是怎么编译成render函数,其具体过程和细节很多,这里就不会过多赘述。本文着重讲述组件是怎么挂载到页面下面来的。首先咱们看看createApp(App).mount(root);这一行代码外面的createApp是怎么生成并且返回mount的。

app对象生成

// 简化后的代码const createApp = ((...args) => {    const app = ensureRenderer().createApp(...args);    const { mount } = app; // 先保留app.mount办法        // 重写app.mount办法    app.mount = (containerOrSelector) => {        // 省略一些代码    };    return app;});

createApp函数不看细节,间接跳到最初一行代码,看返回一个app对象,app外面有一个app.mount函数,里面就能够这么createApp().mount()调用了。

其中的细节也很简略,createApp外面通过ensureRenderer()提早创立渲染器,执行createApp(...args)返回一个app对象,对象外面有mount函数,通切片编程的形式,重写了app.mount的函数,最初返回app这个对象。

当初的疑难来到了app是怎么生成的,app对象外面都有什么,这就看ensureRenderer这个函数

const forcePatchProp = (_, key) => key === 'value';const patchProp = () => {  // 省略};const nodeOps = {  // 插入标签  insert: (child, parent, anchor) => {    parent.insertBefore(child, anchor || null);  },  // 移除标签  remove: child => {    const parent = child.parentNode;    if (parent) {      parent.removeChild(child);    }  },  // 创立标签  createElement: (tag, isSVG, is, props) => {    const el = doc.createElement(tag);    // 省略了svg标签创立代码    return el;  },  // 创立文本标签  createText: text => doc.createTextNode(text),  // ...还有很多}const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps);function ensureRenderer() {    return renderer || (renderer = createRenderer(rendererOptions)); // 创立渲染器}

这个函数很简略,就是间接返回 createRenderer(rendererOptions)创立渲染器函数。

从这里能够看出,如果咱们vue用不到渲染器相干的就不会调用ensureRenderer,只用到响应式的包的时候,这些代码就会被tree-shaking掉。

这里的参数rendererOptions须要阐明一下,这些都是跟dom的增删改查操作相干的,阐明都在正文外面。

持续深刻createRenderer,从下面的代码const app = ensureRenderer().createApp(...args);能够晓得createRendere会返回一个对象,对象外面会有createApp属性,上面是createRenderer函数

// options: dom操作相干的apifunction createRenderer(options) {    return baseCreateRenderer(options);}function baseCreateRenderer(options, createHydrationFns) {  const patch = () => {    // 省略  };  const processText = () => {    // 省略  };  const render = (vnode, container, isSVG) => {    // 省略  }  // 此处省略很多代码  return {    render,    hydrate,    createApp: createAppAPI(render, hydrate)  };}

看到下面的代码,晓得了ensureRenderer();中就是调用createAppAPI(render, hydrate)返回的对象createApp函数,这时候就来到了createAppAPI(render, hydrate)了。

let uid$1 = 0;function createAppAPI(render, hydrate) {    return function createApp(rootComponent, rootProps = null) {        const context = createAppContext(); // 创立app上下文对象        let isMounted = false; // 挂载标识        const app = (context.app = {            _uid: uid$1++,              _component: rootComponent, // 传入的<App />组件              // 省略了一些代码            use(plugin, ...options) {}, // 应用插件相干,省略            mixin(mixin) {}, // mixin 相干,省略            component(name, component) {}, // 组件挂载, 省略            directive(name, directive) {}, // 指令挂载            mount(rootContainer, isHydrate, isSVG) {                if (!isMounted) {                      // 创立vnode                    const vnode = createVNode(rootComponent, rootProps);                    vnode.appContext = context;                      // 省略了一些代码                      // 渲染vnode                    render(vnode, rootContainer, isSVG);                    isMounted = true;                    app._container = rootContainer;                    rootContainer.__vue_app__ = app;                    return vnode.component.proxy;                }            },              // 省略了一些代码        });        return app;    };}

下面的createApp函数外面返回一个app 对象,对象外面就有组件相干的属性,包含插件相干、 mixin 相干、组件挂载、指令挂载到context上,还有一个值得注意的就是mount函数,和最开始createApp(App).mount(root);不一样,还记得下面的外面是通过重写了,重写外面就会调用这里mount函数了,代码如下

const createApp = ((...args) => {    const app = ensureRenderer().createApp(...args);    const { mount } = app; // 先缓存mount    app.mount = (containerOrSelector) => {          // 获取到容器节点        const container = normalizeContainer(containerOrSelector);        if (!container) return;        const component = app._component;        if (!isFunction(component) && !component.render && !component.template) {            // 传入的App组件,如果不是函数组件,组件没有render  组件没有template,就用容器的innerHTML            component.template = container.innerHTML;        }        // 清空容器的内容        container.innerHTML = '';        // 把组件挂载到容器上        const proxy = mount(container, false, container instanceof SVGElement);        return proxy;    };    return app;});

下面的那个重写mount函数,外面做了一些事件,

  • 解决传入的容器并生成节点;
  • 判断传入的组件是不是函数组件,组件外面有没有render函数、组件外面有没有template属性,没有就用容器的innerHTML作为组件的template
  • 清空容器内容;
  • 运行缓存的mount函数,实现挂载组件;

下面就是createApp(App).mount(root);的大抵运行流程。然而到这里仅仅晓得是怎么生成app的,render函数是怎么生成vnode的?,vnode又是怎么挂载到页面的?上面咱们持续看,mount函数外面都做了什么?

mount组件挂载流程

从上文中,最初会调用mount去挂载组件到页面上。咱们着重看看createApp函数中mount函数做了什么?

function createAppAPI(render, hydrate) {    return function createApp(rootComponent, rootProps = null) {        const context = createAppContext(); // 创立app上下文对象        let isMounted = false; // 挂载标识        const app = (context.app = {              // 省略            mount(rootContainer, isHydrate, isSVG) {                if (!isMounted) {                      // 创立vnode                    const vnode = createVNode(rootComponent, rootProps);                    vnode.appContext = context;                      // 省略了一些代码                      // 渲染vnode                    render(vnode, rootContainer, isSVG);                    isMounted = true;                    app._container = rootContainer;                    rootContainer.__vue_app__ = app;                    return vnode.component.proxy;                }            },              // 省略了一些代码        });        return app;    };}

mount函数外面,次要就做了:

  1. 通过const vnode = createVNode(rootComponent, rootProps);创立vnode。
  2. vnode上挂载appContext
  3. render(vnode, rootContainer, isSVG);vnoderootContainer(容器节点)作为参数传入render函数中去执行。
  4. 设置挂载标识isMounted = true;
  5. ...等等其余属性挂载。

到创立vnode的过程,外面最终会调用_createVNode函数,传入rootComponent(就是咱们编译后的object),而后生成一个vnode对象。

const createVNodeWithArgsTransform = (...args) => {    return _createVNode(...(args));};function _createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, isBlockNode = false) {      // 省略    // 将vnode类型信息编码    const shapeFlag = isString(type)        ? 1 /* ELEMENT */        : isSuspense(type)            ? 128 /* SUSPENSE */            : isTeleport(type)                ? 64 /* TELEPORT */                : isObject(type)                    ? 4 /* STATEFUL_COMPONENT */                    : isFunction(type)                        ? 2 /* FUNCTIONAL_COMPONENT */                        : 0;    const vnode = {        __v_isVNode: true,        ["__v_skip" /* SKIP */]: true,        type,        props,        key: props && normalizeKey(props),        ref: props && normalizeRef(props),        scopeId: currentScopeId,        slotScopeIds: null,        children: null,        component: null,        suspense: null,        ssContent: null,        ssFallback: null,        dirs: null,        transition: null,        el: null,        anchor: null,        target: null,        targetAnchor: null,        staticCount: 0,        shapeFlag,        patchFlag,        dynamicProps,        dynamicChildren: null,        appContext: null    };    normalizeChildren(vnode, children);    // 省略    if (!isBlockNode && currentBlock && (patchFlag > 0 || shapeFlag & 6 /* COMPONENT */) && patchFlag !== 32) {        currentBlock.push(vnode);    }    return vnode;}const createVNode = createVNodeWithArgsTransform;

下面的过程,须要留神rootComponent,就是咱们下面编译后的ApprootComponent大抵格局如下(不分明能够回头看看呢。)

rootComponent = {  render() {},  setup() {}}

创立vnode的流程:

  1. 先是判断shapeFlag,这里type == rootComponent 是一个对象,就晓得这时候shapeFlag = 4
  2. 创立一个vnode对象,其中type == rootComponent
  3. 而后normalizeChildren(vnode, children),这里没有children,跳过
  4. 返回vnode

通过创立createVNode就能够失去一个vnode对象,而后就是拿这个vnode去渲染render(vnode, rootContainer, isSVG);

const render = (vnode, container, isSVG) => {  // vnode是空的  if (vnode == null) {    if (container._vnode) {      // 卸载老vnode      unmount(container._vnode, null, null, true);    }  } else {    // container._vnode 一开始是没有的,所以n1 = null    patch(container._vnode || null, vnode, container, null, null, null, isSVG);  }  flushPostFlushCbs();  container._vnode = vnode; // 节点上挂载老的vnode};

大家看到,render函数的执行,首先会判断传入的vnode是不是为null,如果是null 并且容器节点挂载老的vnode,就须要卸载老vnode,因为新的vnode曾经没有了,如果不是null,执行patch函数。这个流程很简略。上面间接看patch函数:

const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = false) => {              // 省略        const { type, ref, shapeFlag } = n2;        switch (type) {            case Text:                processText(n1, n2, container, anchor);                break;            case Comment:                processCommentNode(n1, n2, container, anchor);                break;            case Static:                if (n1 == null) {                    mountStaticNode(n2, container, anchor, isSVG);                } else {                    patchStaticNode(n1, n2, container, isSVG);                }                break;            case Fragment:                processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);                break;            default:                if (shapeFlag & 1 /* ELEMENT */) {                    processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);                } else if (shapeFlag & 6 /* COMPONENT */) {                    processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);                } else if (shapeFlag & 64 /* TELEPORT */) {                    type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals);                } else if (shapeFlag & 128 /* SUSPENSE */) {                    type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals);                } else {                    warn('Invalid VNode type:', type, `(${typeof type})`);                }        }        // set ref        if (ref != null && parentComponent) {            setRef(ref, n1 && n1.ref, parentSuspense, n2);        }    };

patch执行流程:

  1. 先看看咱们的参数n1 = null(老的vnode), n2 = vnode (新的vnode),container = root,一开始是老的vnode的;
  2. 获取n2的type、shapeFlag,此时咱们的type = { render, setup },shapeFlag = 4
  3. 通过switch...case判断,咱们会走到else if (shapeFlag & 6 /* COMPONENT */)这个分支,因为4 & 6 = 4;
  4. 交给processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);去解决咱们的组件。

上面间接看看processComponent这个函数:

const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {        n2.slotScopeIds = slotScopeIds;        if (n1 == null) {            // keep-alive组件解决            if (n2.shapeFlag & 512 /* COMPONENT_KEPT_ALIVE */) {                parentComponent.ctx.activate(n2, container, anchor, isSVG, optimized);            } else {                mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);            }        } else {            updateComponent(n1, n2, optimized);        }    };

这个processComponent函数的作用次要有两个

  1. 当n1 == null 的时候,去调用函数mountComponent去挂载组件
  2. 当n1不为null,就有有新老vnode的时候,去调用updateComponent去更新组件

咱们这里说挂载流程,就间接看mountComponent

const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {        // 第一步        const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense));              // 省略              // 第二步        setupComponent(instance);        // 省略        // 第三步        setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);    };

去掉一些无关的代码之后,咱们看到mountComponent 其实很简略,外面创立组件instance, 而后调用两个函数setupComponentsetupRenderEffect

  • setupComponent:次要作用是做一些组件的初始化工作什么的
  • setupRenderEffect: 就相当于vue2的渲染watcher一样

1、createComponentInstance

然而咱们先看看组件是怎么通过createComponentInstance创立实例的?

function createAppContext() {    return {        app: null,        config: {}, // 省略        mixins: [],        components: {},        directives: {},        provides: Object.create(null)    };}const emptyAppContext = createAppContext();let uid$2 = 0;function createComponentInstance(vnode, parent, suspense) {    const type = vnode.type;    // inherit parent app context - or - if root, adopt from root vnode    const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;    const instance = {        uid: uid$2++,        vnode,        type,        parent,        appContext,        root: null,        subTree: null,        update: null,        render: null        withProxy: null,        components: null,        // props default value        propsDefaults: EMPTY_OBJ,        // state        ctx: EMPTY_OBJ,        data: EMPTY_OBJ,        props: EMPTY_OBJ,        attrs: EMPTY_OBJ,        // 省略    };    {        instance.ctx = createRenderContext(instance); // 生成一个对象 { $el, $data, $props, .... }    }    instance.root = parent ? parent.root : instance;    instance.emit = emit.bind(null, instance);    return instance;}

大家不要把这么多属性看蒙了,其实createComponentInstance就是初始化一个instance对象,而后返回进来这个instance,就这么简略。

instance.ctx = createRenderContext(instance); 这个对象外面有很多初始化属性,在通过createRenderContext把很多属性都挂载到instance.ctx上,外面都是咱们常见的$el、$data、$props、$attr、$emit、...,这跟咱们首次渲染没啥关系,先不要看好了。

2、setupComponent

下一步就是把生成的instance的对象,放到setupComponent函数作为参数去运行。

function setupComponent(instance) {    const { props, children } = instance.vnode;    const isStateful = isStatefulComponent(instance); // instance.vnode.shapeFlag & 4    // 省略    const setupResult = isStateful        ? setupStatefulComponent(instance)        : undefined;    return setupResult;}

setupComponent做的性能就是,判断咱们的vnode.shapeFlag是不是状态组件,从下面得悉,咱们的vnode.shapeFlag == 4,所以下一步就会去调用setupStatefulComponent(instance),而后返回值setupResult,最初 返回进来。在看看setupStatefulComponent

function setupStatefulComponent(instance, isSSR) {    const Component = instance.type;      // 省略    const { setup } = Component;    if (setup) {          // 省略          // 调用setup        const setupResult = callWithErrorHandling(setup, instance, 0, [shallowReadonly(instance.props), setupContext]);        // 省略        handleSetupResult(instance, setupResult, isSSR);    } else {        finishComponentSetup(instance, isSSR);    }}function callWithErrorHandling(fn, instance, type, args) {    let res;    try {        res = args ? fn(...args) : fn(); // 调用传进来的setup函数    } catch (err) {        handleError(err, instance, type);    }    return res;}function handleSetupResult(instance, setupResult, isSSR) {      // 省略      instance.setupState = proxyRefs(setupResult); // 相当于代理挂载操作 instance.setupState = setupResult    {      exposeSetupStateOnRenderContext(instance); // 相当于代理挂载操作 instance.ctx = setupResult    }    finishComponentSetup(instance, isSSR);}

setupStatefulComponent函数就是调用咱们组件自定义的setup函数,返回一个setupResult对象,依据下面写的,setup返回的就是一个对象:

setupResult = {  name: 'world'}

而后在运行handleSetupResult,看到外面其实没做什么工作,就是调用finishComponentSetup(instance, isSSR);

function finishComponentSetup(instance, isSSR) {    const Component = instance.type;    if (!instance.render) {        instance.render = (Component.render || NOOP);    }    // support for 2.x options    {        currentInstance = instance;        pauseTracking();        applyOptions(instance, Component);        resetTracking();        currentInstance = null;    }}

至此所有的setupComponent 流程都实现了,就是调用setup函数,而后往instance外面挂载很多属性代理。包含前面重要的instance.ctx, 都代理了setupResult。上面咱们看第三步:

3、setupRenderEffect

const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {  // create reactive effect for rendering  instance.update = effect(function componentEffect() {    if (!instance.isMounted) {      let vnodeHook;      const { el, props } = initialVNode;      const subTree = (instance.subTree = renderComponentRoot(instance));            // 省略      patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);      initialVNode.el = subTree.el;      // 省略      instance.isMounted = true;      // 活路    } else {      // 更新过程    }  }, createDevEffectOptions(instance) );};

setupRenderEffect中的effect外面的函数会执行一次。而后就到外面的函数了。

  1. 先通过instance.isMounted判断是否曾经挂载了。没有挂载过的就去执行挂载操作,挂载过的就执行更新操作
  2. 通过renderComponentRoot函数生成subTree
  3. 调用path进行递归挂载
  4. 更新instance.isMounted标识

生成subTree

renderComponentRoot是生成subTree的,其实外面就是执行咱们的App组件的render函数。

function renderComponentRoot(instance) {    const { type: Component, vnode, proxy, withProxy, props, propsOptions: [propsOptions], slots, attrs, emit, render, renderCache, data, setupState, ctx } = instance;    let result;    try {        let fallthroughAttrs;        if (vnode.shapeFlag & 4) {              // 拿到instance.proxy            const proxyToUse = withProxy || proxy;              // 调用install.render函数,并扭转this的指向            result = normalizeVNode(render.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx));        } else {           // 函数组件挂载        }          // 省略    } catch (err) {        blockStack.length = 0;        handleError(err, instance, 1 /* RENDER_FUNCTION */);        result = createVNode(Comment);    }    setCurrentRenderingInstance(prev);    return result;}

renderComponentRoot的次要性能就是调用install.render函数,并扭转this的指向到instance.proxy。从下面咱们晓得,

  • instance.proxy有数据代理,就是拜访instance.proxy.name === 'world'
  • result = normalizeVNode(render.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx));这里的render函数就是咱们app编译过的redner函数,

    const _hoisted_1 = /*#__PURE__*/createTextVNode("Hello "); // 动态代码晋升const _hoisted_2 = { class: "blue" }; // 动态代码晋升const render = /*#__PURE__*/_withId((_ctx, _cache, $props, $setup, $data, $options) => {  return (openBlock(), createBlock("h1", null, [    _hoisted_1,    createVNode("span", _hoisted_2, toDisplayString(_ctx.name), 1 /* TEXT */)  ]))});

而后执行render函数,会把instance的ctx、props、setupState等等参数传进去。咱们看看render函数执行就是先执行一个openBlock()

const blockStack = []; // block栈let currentBlock = null; // 以后的blockfunction openBlock(disableTracking = false) {    blockStack.push((currentBlock = disableTracking ? null : []));}function closeBlock() {    blockStack.pop();    currentBlock = blockStack[blockStack.length - 1] || null;}

从下面能够看出,执行一个openBlock(),就是新建一个数据,赋值到currentBlock,而后pushblockStack,所以以后的

blockStack = [[]]currentBlock = []

而后回执先执行createVNode("span", _hoisted_2, toDisplayString(_ctx.name), 1),这就是创立span节点的vnode,toDisplayString(_ctx.name)就是取ctx.name的值,等价于toDisplayString(_ctx.name) === 'world',createVNode下面会有讲过,通过createVNode创立进去的对象大略就是

// 具体代码看前文有写function createVNode () {  const vnode = {    appContext: null,    children: "world",    props: {class: "blue"},    type: "span",    // 省略很多  }  // 这里有判断,如果是动静block就push  // currentBlock.push(vnode);}// 执行过后// currentBlock = [span-vnode]// blockStack = [[span-vnode]]

而后就是执行 createBlock("h1", null, [_hoisted_1, span-vnode])

function createBlock(type, props, children, patchFlag, dynamicProps) {    const vnode = createVNode(type, props, children, patchFlag, dynamicProps, true);    // 保留动静的block,下次更新只更新这个,    vnode.dynamicChildren = currentBlock || EMPTY_ARR;    // 清空以后的block    closeBlock();    // 如果currentBlock还有的话就持续push到currentBlock    if (currentBlock) {        currentBlock.push(vnode);    }    return vnode;}

createBlock也是调用createVNode,这样子就生成一个h1的vnode了,而后执行vnode.dynamicChildren = currentBlock,在清空block并返回vnode。如下大抵如下

vnode = {    "type": "h1",    "children": [        {            "children": "Hello ",            "shapeFlag": 8,            "patchFlag": 0,        },        {            "type": "span",            "props": {                "class": "blue"            },            "children": "world",            "shapeFlag": 9,            "patchFlag": 1,        }    ],    "shapeFlag": 17,    "patchFlag": 0,    "dynamicChildren": [        {            "type": "span",            "props": {                "class": "blue"            },            "children": "world",            "shapeFlag": 9,            "patchFlag": 1,        }    ],    "appContext": null}

patch subTree

而后调用patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);,又到patch,这会type=== 'h1',会调用patch外面的。

const { type, ref, shapeFlag } = n2;if (shapeFlag & 1 /* ELEMENT */) {  processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);}// ---const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {  isSVG = isSVG || n2.type === 'svg';  if (n1 == null) {    mountElement(n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);  } else {    patchElement(n1, n2, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);  }};

调用processElement会调用mountElement去挂载

const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {  let el;  let vnodeHook;  const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode;  {    // 创立h1元素    el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is, props);    // chilren是文本的    if (shapeFlag & 8) {      hostSetElementText(el, vnode.children);    } else if (shapeFlag & 16) { // chilren是数组的      // 递归挂载children      mountChildren(vnode.children, el, null, parentComponent, parentSuspense, isSVG && type !== 'foreignObject', slotScopeIds, optimized || !!vnode.dynamicChildren);    }    // 省略    // 把创立的h1挂载到root上      hostInsert(el, container, anchor);};

mountElement挂载流程,显示创立元素,而后判断子元素是数组还是文本,如果孩子是文本就间接创立文本,插入到元素中,如果是数组,就调用mountChildren函数,

const mountChildren = (children, container, anchor, parentComponent, parentSuspense, isSVG, optimized, slotScopeIds, start = 0) => {  for (let i = start; i < children.length; i++) {    patch(null, child, container, anchor, parentComponent, parentSuspense, isSVG, optimized, slotScopeIds);  }};

mountChildren函数外面就循环所有的孩子,而后一个个调用patch去插入。

最初插入递归path调用实现之后生成的树节点el,会调用hostInsert(el, container, anchor);插入到root中。而后渲染到页面中去。

vue3组件挂载流程

写的不好的中央欢送批评指正

博客链接