欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue 中 template 有且只能一個(gè) root的原因解析(源碼分析)

 更新時(shí)間:2020年04月11日 09:13:31   作者:五柳  
這篇文章主要介紹了Vue 中 template 有且只能一個(gè) root的原因解析,本文從源碼角度分析給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

引言

今年, 疫情 并沒(méi)有影響到各種面經(jīng)的正常出現(xiàn),可謂是絡(luò)繹不絕(學(xué)不動(dòng)...)。然后,在前段時(shí)間也看到一個(gè)這樣的關(guān)于 Vue 的問(wèn)題, 為什么每個(gè)組件 template 中有且只能一個(gè) root?

可能,大家在平常開(kāi)發(fā)中,用的較多就是 template 寫(xiě) html 的形式。當(dāng)然,不排除用 JSXrender() 函數(shù)的。但是,究其本質(zhì),它們最終都會(huì)轉(zhuǎn)化成 render() 函數(shù)。然后,再由 render() 函數(shù)轉(zhuǎn)為 Vritual DOM (以下統(tǒng)稱(chēng) VNode )。而 render() 函數(shù)轉(zhuǎn)為 VNode 的過(guò)程,是由 createElement() 函數(shù)完成的。

因此,本次文章將會(huì)先講述 Vue 為什么限制 template 有且只能一個(gè) root 。然后,再分析 Vue 如何規(guī)避出現(xiàn)多 root 的情況。那么,接下來(lái)我們就從源碼的角度去深究一下這個(gè)過(guò)程!

一、為什么限制 template 有且只能有一個(gè) root

這里,我們會(huì)分兩個(gè)方面講解,一方面是 createElement() 的執(zhí)行過(guò)程和定義,另一方面是 VNode 的定義。

1.1 createElement()

createElement() 函數(shù)在源碼中,被設(shè)計(jì)為 render() 函數(shù)的參數(shù)。所以 官方文檔 也講解了,如何使用 render() 函數(shù)的方式創(chuàng)建組件。

createElement() 會(huì)在 _render 階段執(zhí)行:

...
const { render, _parentVnode } = vm.$options
...
vnode = render.call(vm._renderProxy, vm.$createElement);

可以很簡(jiǎn)單地看出,源碼中通過(guò) call() 將當(dāng)前實(shí)例作為 context 上下文以及 $createElement 作為參數(shù)傳入。

Vue2x 源碼中用了大量的 call 和 apply,例如經(jīng)典的 $set() API 實(shí)現(xiàn)數(shù)組變化的響應(yīng)式處理就用的很是精妙,大家有興趣可以看看。

$createElement 的定義又是這樣:

vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

需要注意的是這個(gè)是我們手寫(xiě) render() 時(shí)調(diào)用的,如果是寫(xiě) template 則會(huì)調(diào)用另一個(gè) vm._c 方法。兩者的區(qū)別在于 createElement() 最后的參數(shù)前者為 true,后者為 false。

而到這里,這個(gè) createElement() 實(shí)質(zhì)是調(diào)用了 _createElement() 方法,它的定義:

export function _createElement (
 context: Component, // vm實(shí)例
 tag?: string | Class<Component> | Function | Object, // DOM標(biāo)簽
 data?: VNodeData, // vnode數(shù)據(jù)
 children?: any, 
 normalizationType?: number
): VNode | Array<VNode> {
 ...
}

現(xiàn)在,見(jiàn)到了我們平常使用的 createElement()廬山真面目 。這里,我們并不看函數(shù)內(nèi)部的執(zhí)行邏輯,這里分析一下這五個(gè)參數(shù):

  • context ,是 Vue_render 階段傳入的當(dāng)前實(shí)例
  • tag ,是我們使用 createElement 時(shí)定義的根節(jié)點(diǎn) HTML 標(biāo)簽名
  • data ,是我們使用 createElement 是傳入的該節(jié)點(diǎn)的屬性,例如 class 、 styleprops 等等
  • children ,是我們使用 createElement 是傳入的該節(jié)點(diǎn)包含的子節(jié)點(diǎn),通常是一個(gè)數(shù)組
  • normalizationType ,是用于判斷拍平子節(jié)點(diǎn)數(shù)組時(shí),要用簡(jiǎn)單迭代還是遞歸處理,前者是針對(duì)簡(jiǎn)單二維,后者是針對(duì)多維。

可以看出, createElement() 的設(shè)計(jì),是針對(duì)一個(gè)節(jié)點(diǎn),然后帶 children 的組件的 VNode 的創(chuàng)建。并且,它并沒(méi)有留給你進(jìn)行多 root 的創(chuàng)建的機(jī)會(huì),只能傳一個(gè)根 roottag ,其他都是它的選項(xiàng)。

1.2 VNode

我想大家都知道 Vue2x 用的靜態(tài)類(lèi)型檢測(cè)的方式是 flow ,所以它會(huì)借助 flow 實(shí)現(xiàn)自定義類(lèi)型。而 VNode 就是其中一種。那么,我們看看 VNode 類(lèi)型定義:

前面,我們分析了 createElement() 的調(diào)用時(shí)機(jī),知道它最終返回的就是 VNode。那么,現(xiàn)在我們來(lái)看看 VNode 的定義:

export default class VNode {
 tag: string | void;
 data: VNodeData | void;
 children: ?Array<VNode>;
 text: string | void;
 elm: Node | void;
 ns: string | void;
 context: Component | void; // rendered in this component's scope
 key: string | number | void;
 componentOptions: VNodeComponentOptions | void;
 componentInstance: Component | void; // component instance
 parent: VNode | void; // component placeholder node

 // strictly internal
 raw: boolean; // contains raw HTML? (server only)
 isStatic: boolean; // hoisted static node
 isRootInsert: boolean; // necessary for enter transition check
 isComment: boolean; // empty comment placeholder?
 isCloned: boolean; // is a cloned node?
 isOnce: boolean; // is a v-once node?
 asyncFactory: Function | void; // async component factory function
 asyncMeta: Object | void;
 isAsyncPlaceholder: boolean;
 ssrContext: Object | void;
 fnContext: Component | void; // real context vm for functional nodes
 fnOptions: ?ComponentOptions; // for SSR caching
 devtoolsMeta: ?Object; // used to store functional render context for devtools
 fnScopeId: ?string; // functional scope id support

 constructor (
 tag?: string,
 data?: VNodeData,
 children?: ?Array<VNode>,
 text?: string,
 elm?: Node,
 context?: Component,
 componentOptions?: VNodeComponentOptions,
 asyncFactory?: Function
 ) {
 ...
 }
 ...
}

可以看到 VNode 所具備的屬性還是蠻多的,本次我們就只看 VNode 前面三個(gè)屬性:

  • tag,即 VNode 對(duì)于的標(biāo)簽名
  • data,即 VNode 具備的一些屬性
  • children,即 VNode 的子節(jié)點(diǎn),它是一個(gè) VNode 數(shù)組

顯而易見(jiàn)的是 VNode 的設(shè)計(jì)也是一個(gè) root ,然后由 children 不斷延申下去。這樣和前面 createElement() 的設(shè)計(jì)相呼應(yīng), 不可能會(huì) 出現(xiàn)多 root 的情況。

1.3 小結(jié)

可以看到 VNodecreateElement() 的設(shè)計(jì),就只是針對(duì)單個(gè) root 的情況進(jìn)行處理,最終形成 樹(shù)的結(jié)構(gòu) 。那么,我想這個(gè)時(shí)候 可能有人會(huì)問(wèn)為什么它們被設(shè)計(jì)樹(shù)的結(jié)構(gòu)? 。

而針對(duì)這個(gè)問(wèn)題,有 兩個(gè)方面 ,一方面是樹(shù)形結(jié)構(gòu)的 VNode 轉(zhuǎn)為真實(shí) DOM 后,我們只需要將根 VNode 的真實(shí) DOM 掛載到頁(yè)面中。另一方面是 DOM 本身就是樹(shù)形結(jié)構(gòu),所以 VNode 也被設(shè)計(jì)為樹(shù)形結(jié)構(gòu),而且之后我們分析 template 編譯階段會(huì)提到 AST 抽象語(yǔ)法樹(shù),它也是樹(shù)形結(jié)構(gòu)。所以,統(tǒng)一的結(jié)構(gòu)可以實(shí)現(xiàn)很方便的類(lèi)型轉(zhuǎn)化,即從 ASTRender 函數(shù),從 Render 函數(shù)到 VNode ,最后從 VNode 到真實(shí) DOM 。

并且,可以想一個(gè)情景,如果多個(gè) root ,那么當(dāng)你將 VNode 轉(zhuǎn)為真實(shí) DOM 時(shí),掛載到頁(yè)面中,是不是要遍歷這個(gè) DOM Collection ,然后掛載上去,而這個(gè)階段又是操作 DOM 的階段。大家都知道的一個(gè)東西就是操作 DOM非常昂貴的 。所以,一個(gè) root 的好處在這個(gè)時(shí)候就體現(xiàn)出它的好處了。

其實(shí)這個(gè)過(guò)程,讓我想起 紅寶書(shū) 中在講文檔碎片的時(shí)候,提倡把要?jiǎng)?chuàng)建的 DOM 先添加到文檔碎片中,然后將文檔碎片添加到頁(yè)面中。(PS:想想第一次看紅寶書(shū)是去年 4 月份,剛開(kāi)始學(xué)前端,不經(jīng)意間過(guò)了快一年了....)

二、如何規(guī)避出現(xiàn)多 root 的情況

 2.1 template 編譯過(guò)程

在我們平常的開(kāi)發(fā)中,通常是在 .vue 文件中寫(xiě) <template> ,然后通過(guò)在 <template> 中創(chuàng)建一個(gè) div 來(lái)作為 root ,再在 root 中編寫(xiě)描述這個(gè) .vue 文件的 html 標(biāo)簽。當(dāng)然,你也可以直接寫(xiě) render() 函數(shù)。

在文章的開(kāi)始,我們也說(shuō)了在 Vue 中無(wú)論是寫(xiě) template 還是 render ,它最終會(huì)轉(zhuǎn)成 render() 函數(shù)。而平常開(kāi)發(fā)中,我們用 template 的方式會(huì)較多。所以,這個(gè)過(guò)程就需要 Vue 來(lái)編譯 template 。

編譯 template 的這個(gè)過(guò)程會(huì)是這樣:

  • 根據(jù) template 生成 AST (抽象語(yǔ)法樹(shù))
  • 優(yōu)化 AST ,即對(duì) AST 節(jié)點(diǎn)進(jìn)行靜態(tài)節(jié)點(diǎn)或靜態(tài)根節(jié)點(diǎn)的判斷,便于之后 patch 判斷
  • 根據(jù) AST 可執(zhí)行的函數(shù),在 Vue 中針對(duì)這一階段定義了很多 _c 、 _l 之類(lèi)的函數(shù),就其本質(zhì)它們是對(duì) render() 函數(shù)的封裝

這三個(gè)步驟在源碼中的定義:

export const createCompiler = createCompilerCreator(function baseCompile (
 template: string,
 options: CompilerOptions
): CompiledResult {
 // 生成 AST
 const ast = parse(template.trim(), options)
 if (options.optimize !== false) {
 // 優(yōu)化 AST
 optimize(ast, options)
 }
 // 生成可執(zhí)行的函數(shù)
 const code = generate(ast, options)
 return {
 ast,
 render: code.render,
 staticRenderFns: code.staticRenderFns
 }
})

需要注意的是 Vue-CLI 提供了兩個(gè)版本, Runtime-CompilerRuntime ,兩者的區(qū)別,在于前者可以將 template 編譯成 render() 函數(shù),但是后者必須手寫(xiě) render() 函數(shù)

而對(duì)于開(kāi)發(fā)中,如果你寫(xiě)了多個(gè) root 的組件,在 parse 的時(shí)候,即生成 AST 抽象語(yǔ)法樹(shù)的時(shí)候, Vue 就會(huì)過(guò)濾掉多余的 root ,只認(rèn)第一個(gè) root 。

parse 的整個(gè)過(guò)程,其實(shí)就是正則匹配的過(guò)程,并且這個(gè)過(guò)程會(huì)用棧來(lái)存儲(chǔ)起始標(biāo)簽。整個(gè) parse 過(guò)程的流程圖:

然后,我們通過(guò)一個(gè)例子來(lái)分析一下,其中針對(duì)多 root 的處理。假設(shè)此時(shí)我們定義了這樣的 template

<div><span></span></div><div></div>

顯然,它是多 root 的。而在處理第一個(gè) <div> 時(shí),會(huì)創(chuàng)建對(duì)應(yīng)的 ASTElement ,它的結(jié)構(gòu)會(huì)是這樣:

{
 type: 1,
 tag: "div",
 attrsList: [],
 attrsMap: {},
 rawAttrsMap: {},
 parent: undefined,
 children: [],
 start: 0,
 end: 5
}

而此時(shí),這個(gè) ASTElement 會(huì)被添加到 stack 中,然后刪除原字符串中的 <div> ,并且設(shè)置 root 為該 ASTElement 。

然后,繼續(xù)遍歷。對(duì)于 <span> 也會(huì)創(chuàng)建一個(gè) ASTElement 并入棧,然后刪除繼續(xù)下一次。接下來(lái),會(huì)匹配到 </span> ,此時(shí)會(huì)處理標(biāo)簽的結(jié)束,例如于棧頂 ASTElementtag 進(jìn)行匹配,然后出棧。接下來(lái),匹配到 </div> ,進(jìn)行和 span 同樣的操作。

最后,對(duì)于第二個(gè) root<div> ,會(huì)做和上面一樣的操作。但是,在處理 </div> 時(shí),此時(shí)會(huì)進(jìn)入判斷 multiple root 的邏輯,即此時(shí)字符串已經(jīng)處理完了,但是這個(gè)結(jié)束標(biāo)簽對(duì)應(yīng)的 ASTElement 并不等于我們最初定義的 root 。所以此時(shí)就會(huì)報(bào)錯(cuò):

Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.

而且,該 ASTElement 也不會(huì)加入最終的 AST 中,所以之后也不可能會(huì)出現(xiàn)多個(gè) root 的情況。

同時(shí),這個(gè)報(bào)錯(cuò)也提示我們?nèi)绻枚鄠€(gè) root ,需要借助 if 條件判斷來(lái)實(shí)現(xiàn)。

可以看出, template 編譯的最終的目標(biāo)就是構(gòu)建一個(gè) AST 抽象語(yǔ)法樹(shù)。所以,它會(huì)在創(chuàng)建第一個(gè) ASTElement 的時(shí)候就確定 ASTroot ,從而確保 root 唯一性。

2.2 _render 過(guò)程

不了解 Vue 初始化過(guò)程的同學(xué),可能不太清楚 _render 過(guò)程。你可以理解為渲染的過(guò)程。在這個(gè)階段會(huì)調(diào)用 render 方法生成 VNode ,以及對(duì) VNode 進(jìn)行一些處理,最終返回一個(gè) VNode 。

而相比較 template 編譯的過(guò)程, _render 過(guò)程的判斷就比較簡(jiǎn)潔:

if (!(vnode instanceof VNode)) {
 if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
 warn(
 'Multiple root nodes returned from render function. Render function ' +
 'should return a single root node.',
 vm
 );
 }
 vnode = createEmptyVNode();
}

前面在講 createElement 的時(shí)候,也講到了 render() 需要返回 VNode 。所以,這里是防止部分騷操作, return 了包含多個(gè) VNode 的數(shù)組。

結(jié)語(yǔ)

通過(guò)閱讀,我想大家也明白了 為什么 Vue 中 template 有且只能一個(gè) root ? 。 Vue 這樣設(shè)計(jì)的出發(fā)點(diǎn)可能很簡(jiǎn)單,為了減少掛載時(shí) DOM 的操作。但是,它是如何處理多 root 的情況,以及相關(guān)的 VNode 、 AST 、 createElement() 等等關(guān)鍵點(diǎn),個(gè)人認(rèn)為都是很值得深入了解的。

到此這篇關(guān)于Vue 中 template 有且只能一個(gè) root的原因解析(源碼分析)的文章就介紹到這了,更多相關(guān)vue template 有且只能一個(gè) root內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Vue 兩個(gè)字段聯(lián)合校驗(yàn)之修改密碼功能的實(shí)現(xiàn)

    Vue 兩個(gè)字段聯(lián)合校驗(yàn)之修改密碼功能的實(shí)現(xiàn)

    本文以校驗(yàn)兩次密碼的一致性應(yīng)用,給出兩個(gè)可變屬性值的字段之間的聯(lián)合校驗(yàn)的典型解決方案,通過(guò)實(shí)例代碼給大家介紹Vue 兩個(gè)字段聯(lián)合校驗(yàn)之修改密碼功能的實(shí)現(xiàn),需要的朋友一起看看吧
    2021-07-07
  • 項(xiàng)目nginx部署到非根目錄下vue配置方案

    項(xiàng)目nginx部署到非根目錄下vue配置方案

    這篇文章主要介紹了項(xiàng)目nginx部署到非根目錄下vue配置方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Vue Element前端應(yīng)用開(kāi)發(fā)之常規(guī)Element界面組件

    Vue Element前端應(yīng)用開(kāi)發(fā)之常規(guī)Element界面組件

    在我們開(kāi)發(fā)BS頁(yè)面的時(shí)候,往往需要了解常規(guī)界面組件的使用,小到最普通的單文本輸入框、多文本框、下拉列表,以及按鈕、圖片展示、彈出對(duì)話(huà)框、表單處理、條碼二維碼等等,本篇隨筆基于普通表格業(yè)務(wù)的展示錄入的場(chǎng)景介紹這些常規(guī)Element組件的使用
    2021-05-05
  • vue實(shí)現(xiàn)通過(guò)手機(jī)號(hào)發(fā)送短信驗(yàn)證碼登錄的示例代碼

    vue實(shí)現(xiàn)通過(guò)手機(jī)號(hào)發(fā)送短信驗(yàn)證碼登錄的示例代碼

    本文主要介紹了vue實(shí)現(xiàn)通過(guò)手機(jī)號(hào)發(fā)送短信驗(yàn)證碼登錄的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • vue?實(shí)現(xiàn)左滑圖片驗(yàn)證功能

    vue?實(shí)現(xiàn)左滑圖片驗(yàn)證功能

    網(wǎng)頁(yè)中滑動(dòng)圖片驗(yàn)證一直是各大網(wǎng)站、移動(dòng)端的主流校驗(yàn)方式,其主要作用是為了區(qū)分人和機(jī)器以及為了防止機(jī)器人程序暴力登錄或攻擊從而設(shè)置的一種安全保護(hù)方式,這篇文章主要介紹了vue?實(shí)現(xiàn)左滑圖片驗(yàn)證,需要的朋友可以參考下
    2023-04-04
  • 詳解在vue3中使用jsx的配置以及一些小問(wèn)題

    詳解在vue3中使用jsx的配置以及一些小問(wèn)題

    本文主要介紹了在vue3中使用jsx的配置以及一些小問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • 解決Vue在封裝了Axios后手動(dòng)刷新頁(yè)面攔截器無(wú)效的問(wèn)題

    解決Vue在封裝了Axios后手動(dòng)刷新頁(yè)面攔截器無(wú)效的問(wèn)題

    這篇文章主要介紹了解決VUE在封裝了Axios后手動(dòng)刷新頁(yè)面攔截器無(wú)效的問(wèn)題,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-11-11
  • WebPack配置vue多頁(yè)面的技巧

    WebPack配置vue多頁(yè)面的技巧

    這篇文章主要介紹了WebPack配置vue多頁(yè)面的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-05-05
  • vue中axios的使用詳解

    vue中axios的使用詳解

    這篇文章主要為大家詳細(xì)介紹了vue中axios的使用,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-03-03
  • vue使用assign巧妙重置data數(shù)據(jù)方式

    vue使用assign巧妙重置data數(shù)據(jù)方式

    這篇文章主要介紹了vue使用assign巧妙重置data數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03

最新評(píng)論