本文次要依据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函数外面,次要就做了:
- 通过
const vnode = createVNode(rootComponent, rootProps);
创立vnode。 - 在
vnode
上挂载appContext
。 render(vnode, rootContainer, isSVG);
将vnode
、rootContainer
(容器节点)作为参数传入render
函数中去执行。- 设置挂载标识
isMounted = true;
。 - ...等等其余属性挂载。
到创立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
,就是咱们下面编译后的App
,rootComponent
大抵格局如下(不分明能够回头看看呢。)
rootComponent = { render() {}, setup() {}}
创立vnode
的流程:
- 先是判断
shapeFlag
,这里type == rootComponent
是一个对象,就晓得这时候shapeFlag = 4
- 创立一个
vnode
对象,其中type == rootComponent
- 而后
normalizeChildren(vnode, children)
,这里没有children
,跳过 - 返回
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
执行流程:
- 先看看咱们的参数n1 = null(老的vnode), n2 = vnode (新的vnode),container = root,一开始是老的vnode的;
- 获取n2的type、shapeFlag,此时咱们的
type = { render, setup }
,shapeFlag = 4 - 通过switch...case判断,咱们会走到
else if (shapeFlag & 6 /* COMPONENT */)
这个分支,因为4 & 6 = 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
函数的作用次要有两个
- 当n1 == null 的时候,去调用函数
mountComponent
去挂载组件 - 当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
, 而后调用两个函数setupComponent
、setupRenderEffect
。
- 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外面的函数会执行一次。而后就到外面的函数了。
- 先通过
instance.isMounted
判断是否曾经挂载了。没有挂载过的就去执行挂载操作,挂载过的就执行更新操作 - 通过
renderComponentRoot
函数生成subTree - 调用path进行递归挂载
- 更新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
,而后push
到blockStack
,所以以后的
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);};
mountElemen
t挂载流程,显示创立元素,而后判断子元素是数组还是文本,如果孩子是文本就间接创立文本,插入到元素中,如果是数组,就调用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组件挂载流程
写的不好的中央欢送批评指正