本文主要根據vue3源碼去理解清楚vue3的組件掛載流程(最後附流程圖),根據我的閱讀源碼去解釋,vue的組件是怎麼從.vue
單文件組件一步步插入到真實DOM中,並渲染到頁面上。css
那下面的簡單代碼去作個例子說明,先看看vue3的寫法。html
App.vuevue
<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.jsnode
import { createApp } from "vue"; import App from "./App.vue"; const root = document.getElementById('app'); console.log(App); createApp(App).mount(root);
index.htmlreact
<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
上。到這裏就會有些疑問❓api
先看看第一個問題,咱們上面代碼有打印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
代碼,編譯後代碼以下:app
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
的。
// 簡化後的代碼 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操做相關的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) }; }
看到上面的代碼,知道了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
去掛載組件到頁面上。咱們着重看看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
執行流程:
type = { render, setup }
,shapeFlag = 4else 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
函數的做用主要有兩個
mountComponent
去掛載組件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
。
可是咱們先看看組件是怎麼經過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、...,這跟咱們初次渲染沒啥關係,先不要看好了。
下一步就是把生成的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
。下面咱們看第三步:
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
函數生成subTreerenderComponentRoot
是生成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
。從上面咱們知道,
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; // 當前的block function 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(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中。而後渲染到頁面中去。
寫的很差的地方歡迎批評指正