成人在线亚洲_国产日韩视频一区二区三区_久久久国产精品_99国内精品久久久久久久

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

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

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

引言

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

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

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

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

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

1.1 createElement()

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

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

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

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

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

$createElement 的定義又是這樣:

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

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

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

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

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

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

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

1.2 VNode

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

前面,我們分析了 createElement() 的調(diào)用時機,知道它最終返回的就是 VNode。那么,現(xiàn)在我們來看看 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 的子節(jié)點,它是一個 VNode 數(shù)組

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

1.3 小結(jié)

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

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

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

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

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

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

2.1 template 編譯過程

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

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

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

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

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

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 提供了兩個版本, Runtime-Compiler 和 Runtime ,兩者的區(qū)別,在于前者可以將 template 編譯成 render() 函數(shù),但是后者必須手寫 render() 函數(shù)

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

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

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

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

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

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

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

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

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

最后,對于第二個 root 的 <div> ,會做和上面一樣的操作。但是,在處理 </div> 時,此時會進入判斷 multiple root 的邏輯,即此時字符串已經(jīng)處理完了,但是這個結(jié)束標簽對應的 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 中,所以之后也不可能會出現(xiàn)多個 root 的情況。

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

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

2.2 _render 過程

不了解 Vue 初始化過程的同學,可能不太清楚 _render 過程。你可以理解為渲染的過程。在這個階段會調(diào)用 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 的數(shù)組。

結(jié)語

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

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

標簽: Vue
相關文章:
成人在线亚洲_国产日韩视频一区二区三区_久久久国产精品_99国内精品久久久久久久
欧美精品 日韩| 亚洲国产片色| 亚洲精品色图| 久久精品欧美日韩精品| 国内精品嫩模私拍在线| 91久久精品国产91性色tv| 亚洲人成精品久久久久久| 欧美日韩在线不卡一区| 久久久久久夜精品精品免费| 粉嫩av一区二区三区在线播放 | 精久久久久久| 亚洲欧洲性图库| 91久久极品少妇xxxxⅹ软件| 国产精品成人一区二区艾草| 国产农村妇女毛片精品久久麻豆| 丁香亚洲综合激情啪啪综合| 日韩一级大片在线观看| 福利91精品一区二区三区| 日韩欧美国产系列| 粉嫩在线一区二区三区视频| 欧美大片日本大片免费观看| av亚洲精华国产精华| 久久久久国产精品麻豆| 欧美精品aa| 中文字幕在线不卡| 最新国产乱人伦偷精品免费网站| 亚洲黄色在线视频| 国产在线视频不卡二| 欧美精品在线观看播放| 韩国一区二区三区| 欧美一区二区三区免费观看视频 | 成人免费高清在线观看| 久久综合九色综合久久久精品综合 | 欧美日韩国产成人精品| 国产精品高潮久久久久无| 亚洲人成网站在线观看播放| 亚洲靠逼com| 色综合久久99| 美女精品一区二区| 91.麻豆视频| 成人免费观看视频| 日本一区二区在线不卡| 亚洲高清自拍| 欧美一级片在线看| 成人av网址在线| 国产精品久久久久久一区二区三区| 91久久视频| 水野朝阳av一区二区三区| 在线观看免费视频综合| 国产一区二区0| 久久久久久久综合| 亚洲激情另类| 日韩精品成人一区二区在线| 欧美午夜电影在线播放| 国产jizzjizz一区二区| 国产亚洲欧美一级| 国产精品一卡| 国产一区二区调教| 久久久精品欧美丰满| 99国产精品久久久久老师 | 久久久亚洲一区| 久久精品av麻豆的观看方式| 国产精品一区免费视频| 久久精品一区二区三区不卡牛牛| 亚洲动漫精品| 免费的成人av| 日韩免费在线观看| 欧美日韩综合网| 亚洲国产乱码最新视频| 午夜视频一区| 亚洲伦理在线免费看| 日本丶国产丶欧美色综合| 先锋影音一区二区三区| 亚洲一区二三区| 欧美日韩电影一区| 99精品久久99久久久久| 亚洲色图视频免费播放| 在线观看日韩国产| 欧美+亚洲+精品+三区| 亚洲国产精品人人做人人爽| 欧美精品丝袜久久久中文字幕| 91丨九色丨黑人外教| 亚洲午夜影视影院在线观看| 欧美一区二区日韩一区二区| 亚洲片区在线| 国产成人一级电影| 亚洲精品v日韩精品| 欧美精品日韩一区| 怡红院精品视频在线观看极品| 麻豆国产精品视频| 国产精品灌醉下药二区| 欧美日韩高清一区二区不卡| 亚洲午夜精品久久| 国产一区在线精品| 亚洲欧美日韩中文字幕一区二区三区 | 国产精品久久久久四虎| 在线看不卡av| 女人色偷偷aa久久天堂| 蜜臀av一级做a爰片久久| 中文字幕乱码亚洲精品一区| 欧美三级资源在线| 久久国产福利国产秒拍| 亚洲欧洲av一区二区三区久久| 欧美日韩综合不卡| 日韩网站在线| 高清beeg欧美| 午夜不卡在线视频| 26uuu国产在线精品一区二区| 亚洲一区精彩视频| 91麻豆免费观看| 老司机午夜精品| 中文字幕一区二区三区av| 在线成人午夜影院| 国产精品免费在线| 午夜日韩av| 国内成人自拍视频| 亚洲一区二区三区四区在线观看| 久久新电视剧免费观看| 精品视频全国免费看| 在线亚洲激情| 欧美不卡视频| 国产精一区二区三区| 亚洲夂夂婷婷色拍ww47| 国产午夜精品久久久久久久 | 国模套图日韩精品一区二区 | 99久久精品一区| 久久99精品视频| 亚洲乱码国产乱码精品精98午夜 | 亚洲精品一区二区三区av| 大胆欧美人体老妇| 免费欧美在线视频| 亚洲国产精品尤物yw在线观看| 亚洲国产精品传媒在线观看| 日韩一区二区视频| 在线国产电影不卡| 亚洲一区二区三区高清| 国模精品一区二区三区| 不卡的av在线| 国产精品1区2区3区| 日本欧美肥老太交大片| 亚洲午夜久久久| 亚洲青青青在线视频| 中文乱码免费一区二区| 久久久亚洲精华液精华液精华液| 欧美日韩精品高清| 日本久久一区二区| 亚洲欧美网站| 9色精品在线| 在线观看福利一区| 国内一区二区三区| 欧美国产先锋| 欧美激情日韩| 色综合色综合色综合 | 欧美激情五月| 99re这里只有精品视频首页| 福利一区福利二区| 国产福利一区二区| 国产在线视频不卡二| 久久精品久久综合| 麻豆高清免费国产一区| 蜜乳av一区二区| 三级久久三级久久| 亚洲国产精品久久人人爱蜜臀| 夜夜嗨av一区二区三区中文字幕 | 久久久xxx| 免费日韩一区二区| 国产亚洲一区在线播放| 91久久精品国产91久久性色tv| 亚洲成色www久久网站| 极品中文字幕一区| 亚洲欧洲午夜| 国产亚洲一区在线| 亚洲一区观看| 午夜综合激情| 一本色道亚洲精品aⅴ| 91黄色激情网站| 精品视频色一区| 欧美一区二区免费| 精品国产青草久久久久福利| 久久综合av免费| 国产精品免费看片| 亚洲人成精品久久久久| 一区二区国产视频| 丝袜脚交一区二区| 理论电影国产精品| 国产成人免费视频一区| a亚洲天堂av| 欧美精品一区二区高清在线观看| 久久看人人爽人人| √…a在线天堂一区| 亚洲综合丁香婷婷六月香| 天天影视色香欲综合网老头| 麻豆中文一区二区| 国产福利一区在线| 91视视频在线观看入口直接观看www | 韩国欧美国产1区| 成人av午夜电影| 欧美私人啪啪vps| 国产精品日韩一区二区| 老牛影视一区二区三区|