11

This article mainly understands the vue3 component mounting process based on the vue3 source code (the flow chart is attached at the end), and explains how the vue component is .vue single-file component step by step into the real DOM and rendered to the page based on personal reading the source code. on.

Example description

Let's take a look at the writing of vue3 first by following the simple code as an example.

  • 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>

Through these three documents vue3 can put App.vue content to render the page, we take a look at rendering results, as shown below:

image-20210524192812883

Look at the above example: we can know that the App component is passed createApp(App) method in vue, and then the mount(root) method is called to mount it on root . There will be some questions here ❓

  • What does the App component look like after being compiled by Vue?
  • What kind of processing goes through the createApp(App) function and then returns to the mount method?
  • How does the mount method mount App components to root?
  • ...

Let’s take a look at the first question. The code above prints console.log(App) App look specifically at 060adabb06b681 after being compiled to get the following object:

image-20210524193854779

Among them, setup setup function defined by our component, and the function code of 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 */)
  ]))
});

It is not difficult to see here that after the App.vue file is compiled, a render function is obtained. For details, please see online compilation . Of course, there is also the css code, the compiled code is as follows:

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

styleInject method is to create a style tag, style tag is the content of css , and then insert it into the head tag, the simple code is as follows,

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);
}

Rendering process

From the above example, we can see that, in fact, Vue is a single-file component that is compiled and then mounted on the page. The CSS style is inserted into the head. The general flow chart is as follows:

image-20210525102552034

.vue file in the first part is compiled into the render function, the detailed process and details are many, so I won’t go into details here. This article focuses on how the components are mounted on the page. First, we look createApp(App).mount(root); this line of code inside createApp is how to generate and return mount of.

app object generation

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

createApp function does not look at the details, jump to the last line of code, return to see a app objects, app which has a app.mount function, can outside so createApp().mount() call.

The details are also very simple. createApp uses ensureRenderer() delay the creation of the renderer, and executes createApp(...args) to return a app object. The object contains the mount function. Through slice programming, app.mount is rewritten, and the app object is finally returned.

Now the question comes to how app is generated, what is in the app object, it depends ensureRenderer the function 060adabb06b894

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)); // 创建渲染器
}

This function is very simple, that is, directly return createRenderer(rendererOptions) create a renderer function.

It can be seen from this that if we don't use the renderer related to vue, we won't call ensureRenderer. When only the responsive package is used, these codes will be tree-shaking.

The parameter rendererOptions here needs to be explained. These are related to dom , and check operation of 060adabb06b91c, and the explanations are in the comments.

Continue to go deep into createRenderer , from the above code const app = ensureRenderer().createApp(...args); can know that createRendere will return an object, the object will have the createApp attribute, the following is the createRenderer function

// options: dom操作相关的api
function 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)
  };
}

See the above code, know ensureRenderer(); in is to call createAppAPI(render, hydrate) object returned createApp function, this time came createAppAPI(render, hydrate) up.

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;
    };
}

The createApp function above returns an app object. There are component-related attributes in the object, including plugin-related, mixin-related, component mounting, and command mounting to the context. Another thing worth noting is the mount function, and the beginning createApp(App).mount(root); is different. Remember that the above is rewritten. The mount function will be called in the rewrite. The code is as follows

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;
});

The above one rewrites the mount function, which does some things,

  • Process the incoming container and generate nodes;
  • Determine whether the incoming component is a function component, whether there is a render function in the component, whether there is a template attribute in the component, if not, use the container's innerHTML as the component's template ;
  • Empty the contents of the container;
  • Run the cached mount function to implement mounting components;

The above is the general operating process of createApp(App).mount(root); But here only know how to generate app , the render function is how to generate vnode of?, vnode is how to mount the page? Let's continue to see what is done in the mount

Mount component mounting process

From the above, we will finally call mount to mount the component to the page. Let's focus on what the createApp function does in the mount function?

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;
    };
}

In the mount function, the main thing is:

  1. Create a vnode through const vnode = createVNode(rootComponent, rootProps);
  2. In vnode mounted on appContext .
  3. render(vnode, rootContainer, isSVG); passes vnode and rootContainer (container nodes) as parameters to the render function for execution.
  4. Set the mount flag isMounted = true; .
  5. ...And so on other attributes are mounted.

vnode the process of creating _createVNode will eventually be called rootComponent (which is our compiled object) will be passed in vnode object will be generated.

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;

The above process, need to pay attention rootComponent , is compiled above us App , rootComponent roughly the following format (not sure you can look back at it.)

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

The process of creating vnode

  1. First judge shapeFlag , where type == rootComponent is an object, you know that at this time shapeFlag = 4
  2. Create a vnode object, where type == rootComponent
  3. Then normalizeChildren(vnode, children) , there is no children , skip
  4. Return vnode

You can get a vnode object by creating createVNode , and then use this vnode to render 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
};

As you can see, render function first determines whether the incoming vnode is null . If it is null and the container node mounts the old vnode vnode needs to be uninstalled, because the new vnode is gone, if not null , Execute the patch function. The process is very simple. Look directly at the patch function below:

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 execution process:

  1. First look at our parameters n1 = null (old vnode), n2 = vnode (new vnode), container = root, it was the old vnode at the beginning;
  2. Get the type and shapeFlag of n2, at this time our type = { render, setup } , shapeFlag = 4
  3. After switch ... case judgment, we will come else if (shapeFlag & 6 /* COMPONENT */) this branch, because 4 & 6 = 4 ;
  4. processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized); it over to 060adabb06bf32 to process our components.

Let's take a look directly at the function 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);
        }
    };

The processComponent function has two main functions

  1. When n1 == null, call function mountComponent to mount the component
  2. When n1 is not null and there are new and old vnodes, call updateComponent to update the components

We are talking about the mounting process here, just look at 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);
    };

After removing some irrelevant code, we see that mountComponent is actually very simple. It creates a component instance , and then calls two functions setupComponent and setupRenderEffect .

  • setupComponent: The main function is to do some component initialization work or something
  • setupRenderEffect: It is equivalent to the rendering watcher of vue2

1、createComponentInstance

But let's first look at how the component creates an instance 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;
}

Don't let so many properties be createComponentInstance . In fact, 060adabb06c25b is to initialize a instance object, and then return this instance . It's that simple.

instance.ctx = createRenderContext(instance); There are many initialization properties in this object. Many properties are mounted on instance.ctx through createRenderContext. They are all our common $el, $data, $props, $attr, $emit,..., this has nothing to do with our initial rendering, don't be optimistic about it.

2、setupComponent

The next step is to put the generated instance object into the setupComponent function as a parameter to run.

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

Look at setupComponent does is to determine whether our vnode.shapeFlag is a state component. From the above, we know that our vnode.shapeFlag == 4 , so the next step will call setupStatefulComponent(instance) , then return the value setupResult, and finally return. Have a look at 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 function is to call the custom setup function of our component and return a setupResult object. According to what is written above, the setup returns an object:

setupResult = {
  name: 'world'
}

Then when I run handleSetupResult , I see that there is actually no work in it, so I call 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;
    }
}

So far all the setupComponent processes have been completed, that is, call the setup function, and then mount many attribute agents in instance Including the important instance.ctx , they all represent setupResult . Let's look at the third step below:

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) );
};

The function in effect in setupRenderEffect will be executed once. Then go to the function inside.

  1. First judge whether it has been mounted instance.isMounted If it has not been mounted, perform the mount operation, and if it has been mounted, perform the update operation.
  2. Generate subTree by renderComponentRoot
  3. Call path to mount recursively
  4. Update the instance.isMounted logo

Generate subTree

renderComponentRoot generates the subTree, which is actually the render function App

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 main function is to call install.render function, and change this pointing to instance.proxy . We know from the above,

  • instance.proxy has a data proxy, that is, access instance.proxy.name ==='world'
  • result = normalizeVNode(render.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx)); render function here is the redner function compiled by our app.

    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 */)
      ]))
    });

Then execute the render function, and pass in the ctx, props, setupState and other parameters of the instance. Let's take a look at the execution of the render function is to execute an openBlock() first

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

As can be seen from the above, to execute a openBlock() is to create a new data, assign it to currentBlock , and then push to blockStack , so the current

blockStack = [[]]
currentBlock = []

Then the receipt first executes createVNode("span", _hoisted_2, toDisplayString(_ctx.name), 1) , which is the vnode that creates the span node. toDisplayString(_ctx.name) takes the value of toDisplayString(_ctx.name) === 'world' . As mentioned above on createVNode, the object created by createVNode is probably

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

Then execute 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 also calls createVNode to generate a h1 vnode, and then execute vnode.dynamicChildren = currentBlock, clear the block and return to the vnode. It is roughly as follows

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

Then call patch(null, subTree, container, anchor, instance, parentSuspense, isSVG); , and then to patch, which will type==='h1', and call the patch in it.

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);
  }
};

Calling processElement will call mountElement to mount

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);
};

mountElemen t Mount the process, display the creation element, and then determine whether the child element is an array or a text. If the child is a text, create the text directly and insert it into the element. If it is an array, call the mountChildren function.

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 function, all the children are looped, and then patch called one by one to insert.

Finally, insert the tree node el generated after the path hostInsert(el, container, anchor); insert into the root. Then it is rendered to the page.

Vue3 component mounting process

vue3挂载流程

Criticisms and corrections are welcome for bad writing

Blog link


naice
2k 声望161 粉丝

很多事情不是因为有希望才去坚持,而是坚持了才有希望。