日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

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

瀏覽:25日期:2023-01-27 09:05:47

引言

今年, 疫情 并沒有影響到各種面經的正常出現,可謂是絡繹不絕(學不動...)。然后,在前段時間也看到一個這樣的關于 Vue 的問題, 為什么每個組件 template 中有且只能一個 root?

可能,大家在平常開發中,用的較多就是 template 寫 html 的形式。當然,不排除用 JSX 和 render() 函數的。但是,究其本質,它們最終都會轉化成 render() 函數。然后,再由 render() 函數轉為 Vritual DOM (以下統稱 VNode )。而 render() 函數轉為 VNode 的過程,是由 createElement() 函數完成的。

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

一、為什么限制 template 有且只能有一個 root

這里,我們會分兩個方面講解,一方面是 createElement() 的執行過程和定義,另一方面是 VNode 的定義。

1.1 createElement()

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

而 createElement() 會在 _render 階段執行:

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

可以很簡單地看出,源碼中通過 call() 將當前實例作為 context 上下文以及 $createElement 作為參數傳入。

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

$createElement 的定義又是這樣:

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

需要注意的是這個是我們手寫 render() 時調用的,如果是寫 template 則會調用另一個 vm._c 方法。兩者的區別在于 createElement() 最后的參數前者為 true,后者為 false。

而到這里,這個 createElement() 實質是調用了 _createElement() 方法,它的定義:

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

現在,見到了我們平常使用的 createElement() 的 廬山真面目 。這里,我們并不看函數內部的執行邏輯,這里分析一下這五個參數:

context ,是 Vue 在 _render 階段傳入的當前實例 tag ,是我們使用 createElement 時定義的根節點 HTML 標簽名 data ,是我們使用 createElement 是傳入的該節點的屬性,例如 class 、 style 、 props 等等 children ,是我們使用 createElement 是傳入的該節點包含的子節點,通常是一個數組 normalizationType ,是用于判斷拍平子節點數組時,要用簡單迭代還是遞歸處理,前者是針對簡單二維,后者是針對多維。

可以看出, createElement() 的設計,是針對一個節點,然后帶 children 的組件的 VNode 的創建。并且,它并沒有留給你進行多 root 的創建的機會,只能傳一個根 root 的 tag ,其他都是它的選項。

1.2 VNode

我想大家都知道 Vue2x 用的靜態類型檢測的方式是 flow ,所以它會借助 flow 實現自定義類型。而 VNode 就是其中一種。那么,我們看看 VNode 類型定義:

前面,我們分析了 createElement() 的調用時機,知道它最終返回的就是 VNode。那么,現在我們來看看 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 前面三個屬性:

tag,即 VNode 對于的標簽名 data,即 VNode 具備的一些屬性 children,即 VNode 的子節點,它是一個 VNode 數組

顯而易見的是 VNode 的設計也是一個 root ,然后由 children 不斷延申下去。這樣和前面 createElement() 的設計相呼應, 不可能會 出現多 root 的情況。

1.3 小結

可以看到 VNode 和 createElement() 的設計,就只是針對單個 root 的情況進行處理,最終形成 樹的結構 。那么,我想這個時候 可能有人會問為什么它們被設計樹的結構?

而針對這個問題,有 兩個方面 ,一方面是樹形結構的 VNode 轉為真實 DOM 后,我們只需要將根 VNode 的真實 DOM 掛載到頁面中。另一方面是 DOM 本身就是樹形結構,所以 VNode 也被設計為樹形結構,而且之后我們分析 template 編譯階段會提到 AST 抽象語法樹,它也是樹形結構。所以,統一的結構可以實現很方便的類型轉化,即從 AST 到 Render 函數,從 Render 函數到 VNode ,最后從 VNode 到真實 DOM 。

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

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

其實這個過程,讓我想起 紅寶書 中在講文檔碎片的時候,提倡把要創建的 DOM 先添加到文檔碎片中,然后將文檔碎片添加到頁面中。(PS:想想第一次看紅寶書是去年 4 月份,剛開始學前端,不經意間過了快一年了....)

二、如何規避出現多 root 的情況

2.1 template 編譯過程

在我們平常的開發中,通常是在 .vue 文件中寫 <template> ,然后通過在 <template> 中創建一個 div 來作為 root ,再在 root 中編寫描述這個 .vue 文件的 html 標簽。當然,你也可以直接寫 render() 函數。

在文章的開始,我們也說了在 Vue 中無論是寫 template 還是 render ,它最終會轉成 render() 函數。而平常開發中,我們用 template 的方式會較多。所以,這個過程就需要 Vue 來編譯 template 。

編譯 template 的這個過程會是這樣:

根據 template 生成 AST (抽象語法樹) 優化 AST ,即對 AST 節點進行靜態節點或靜態根節點的判斷,便于之后 patch 判斷 根據 AST 可執行的函數,在 Vue 中針對這一階段定義了很多 _c 、 _l 之類的函數,就其本質它們是對 render() 函數的封裝

這三個步驟在源碼中的定義:

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

需要注意的是 Vue-CLI 提供了兩個版本, Runtime-Compiler 和 Runtime ,兩者的區別,在于前者可以將 template 編譯成 render() 函數,但是后者必須手寫 render() 函數

而對于開發中,如果你寫了多個 root 的組件,在 parse 的時候,即生成 AST 抽象語法樹的時候, Vue 就會過濾掉多余的 root ,只認第一個 root 。

而 parse 的整個過程,其實就是正則匹配的過程,并且這個過程會用棧來存儲起始標簽。整個 parse 過程的流程圖:

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

然后,我們通過一個例子來分析一下,其中針對多 root 的處理。假設此時我們定義了這樣的 template :

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

顯然,它是多 root 的。而在處理第一個 <div> 時,會創建對應的 ASTElement ,它的結構會是這樣:

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

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

然后,繼續遍歷。對于 <span> 也會創建一個 ASTElement 并入棧,然后刪除繼續下一次。接下來,會匹配到 </span> ,此時會處理標簽的結束,例如于棧頂 ASTElement 的 tag 進行匹配,然后出棧。接下來,匹配到 </div> ,進行和 span 同樣的操作。

最后,對于第二個 root 的 <div> ,會做和上面一樣的操作。但是,在處理 </div> 時,此時會進入判斷 multiple root 的邏輯,即此時字符串已經處理完了,但是這個結束標簽對應的 ASTElement 并不等于我們最初定義的 root 。所以此時就會報錯:

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 也不會加入最終的 AST 中,所以之后也不可能會出現多個 root 的情況。

同時,這個報錯也提示我們如果要用多個 root ,需要借助 if 條件判斷來實現。

可以看出, template 編譯的最終的目標就是構建一個 AST 抽象語法樹。所以,它會在創建第一個 ASTElement 的時候就確定 AST 的 root ,從而確保 root 唯一性。

2.2 _render 過程

不了解 Vue 初始化過程的同學,可能不太清楚 _render 過程。你可以理解為渲染的過程。在這個階段會調用 render 方法生成 VNode ,以及對 VNode 進行一些處理,最終返回一個 VNode 。

而相比較 template 編譯的過程, _render 過程的判斷就比較簡潔:

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 的時候,也講到了 render() 需要返回 VNode 。所以,這里是防止部分騷操作, return 了包含多個 VNode 的數組。

結語

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

到此這篇關于Vue 中 template 有且只能一個 root的原因解析(源碼分析)的文章就介紹到這了,更多相關vue template 有且只能一個 root內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Vue
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
国产欧美另类| 日韩和的一区二在线| 亚洲高清久久| 激情五月综合网| 欧美专区18| 日韩在线黄色| 久久av导航| 欧美激情国产在线| 偷拍精品精品一区二区三区| 粉嫩av一区二区三区四区五区 | 手机在线电影一区| 日韩黄色大片| 亚洲自拍另类| 欧美一区激情| a日韩av网址| 亚洲欧美日韩精品一区二区| 日韩精品a在线观看91| 美女性感视频久久| 久久一区二区三区电影| 亚洲人成毛片在线播放女女| 麻豆久久一区| 免费观看不卡av| 日韩一二三区在线观看| 成人污污视频| 西西人体一区二区| 久久免费福利| 视频在线观看一区| 国产亚洲欧美日韩精品一区二区三区 | 免费亚洲一区| 91久久久精品国产| 国产毛片精品久久| 国产尤物精品| 久久99精品久久久野外观看| 国产一区欧美| 欧美黑人巨大videos精品| 日本久久成人网| 欧美另类中文字幕 | 日韩精品一区二区三区中文| 免费在线小视频| 日韩精品亚洲专区在线观看| 人人香蕉久久| 欧美国产中文高清| 热久久免费视频| 日韩免费av| 国产精品久久久一区二区| 夜夜嗨网站十八久久| 97久久亚洲| 米奇777超碰欧美日韩亚洲| 国产精品久久久免费| 亚洲欧美日本国产专区一区| 国产成人精品一区二区三区免费| 三级欧美在线一区| 日韩视频网站在线观看| 国产香蕉精品| 先锋影音国产一区| 久久精品青草| 成人在线超碰| 国产精品美女午夜爽爽| 久久av一区二区三区| 日韩三区免费| 精品久久精品| 91亚洲无吗| 亚洲一区导航| 久久不射中文字幕| 免费av一区二区三区四区| 成人污污视频| 国产黄色一区| 国产欧美三级| 日本久久一区| 亚洲一区二区小说| 国产农村妇女精品一二区| 1024精品一区二区三区| 国产精品99一区二区三区| 国产日韩欧美三级| 日韩国产欧美在线播放| 免费美女久久99| 999国产精品999久久久久久| 久久免费影院| 你懂的网址国产 欧美| 国产亚洲一区| 国产一级成人av| 日本中文字幕一区二区视频 | 国产91精品对白在线播放| 四季av一区二区凹凸精品| 国产激情精品一区二区三区| 国产日韩亚洲| 久久福利在线| 国产精品成人3p一区二区三区| 日韩精品视频在线看| 亚洲综合不卡| 亚洲免费网址| 日本一区中文字幕| 日韩三级视频| 人人精品久久| 亚洲精品观看| 日韩av影院| 国产精品尤物| 精品亚洲a∨一区二区三区18| 精品免费av| 日韩欧美国产精品综合嫩v| 91偷拍一区二区三区精品| 欧美xxxx中国| 日韩精品一区二区三区免费观影 | 亚洲二区视频| 不卡一区2区| 亚洲一区不卡| 天堂va在线高清一区| 国产日韩中文在线中文字幕 | 日av在线不卡| 国产探花一区| 精品国产乱码| 欧美日韩在线网站| 亚洲精品小说| 视频一区视频二区在线观看| 日韩精品五月天| 毛片不卡一区二区| 一区二区三区四区日本视频| 成人久久久久| 亚洲免费播放| 日本久久二区| 国产a久久精品一区二区三区| 日韩精品永久网址| 亚洲精品888| 亚洲人成精品久久久| 国产精品一区二区三区www | 成人国产精品久久| 国产中文字幕一区二区三区| 日韩欧美一区二区三区在线视频 | 日韩成人精品一区二区三区| 你懂的网址国产 欧美| 日韩一区二区三区在线免费观看| 欧美在线资源| 欧美日韩视频免费看| 国产中文字幕一区二区三区| 激情视频一区二区三区| 日韩在线观看一区二区三区| 麻豆精品新av中文字幕| 中文字幕人成乱码在线观看 | 国产精品一区亚洲| 久久久久久黄| 日产欧产美韩系列久久99| 91免费精品| 中文字幕成人| 色综合狠狠操| 日本欧洲一区二区| 精品美女在线视频| 噜噜噜躁狠狠躁狠狠精品视频| 国产精品欧美日韩一区| 蜜桃成人av| 精品一区视频| 亚洲精品中文字幕乱码| 国产精品久久久久9999高清| 国产99精品一区| 国产亚洲久久| 国产字幕视频一区二区| 国产一精品一av一免费爽爽| 欧洲毛片在线视频免费观看| 国产欧美自拍| 欧美成人基地| 91成人精品在线| 99久久久国产精品美女| 日本不卡高清| 免费观看久久av| 精品一级视频| 日韩av中文字幕一区二区三区| 久久uomeier| 国产亚洲欧美日韩精品一区二区三区 | 日韩欧美中文字幕电影| 久久一区二区三区喷水| 欧美成人一二区| 日韩一区二区三区精品| 在线视频观看日韩| 精品黄色一级片| 亚洲精品免费观看| 人人精品亚洲| 久久精品理论片| 日韩va欧美va亚洲va久久| 欧美69视频| 欧美www视频在线观看| 国产欧美久久一区二区三区| 六月天综合网| 欧美精品一区二区三区精品| 国产精品白丝久久av网站| 人人爽香蕉精品| 99成人在线| 久久精品亚洲欧美日韩精品中文字幕| 国产精品99久久免费观看| 蜜臀久久99精品久久久画质超高清| 日本精品不卡| 久久永久免费| 国产精品对白久久久久粗| 免费在线观看一区二区三区| 亚洲免费激情| 激情视频一区二区三区| 久久男人天堂| 成人污污视频| 精品国产乱码久久久久久1区2匹| 日韩二区三区在线观看| 亚洲精品一二三**| 蜜桃一区二区三区在线观看|