爲何 Vue 實例只能有一個根元素?

做者:Horace
ps:新人小白一枚,若有錯誤,歡迎指出~css

問題提出

筆者如今是一個大三將要實習的學生,前段時間在掘金看到了一篇文章《vue 248個知識點(面試題)爲你保駕護航》,做者在裏面寫到他在面試一個5年前端工做經驗的小夥子中的一些問題,其中有一個問題引發了個人注意,這個問題就是:不錯,那我問下你 「vue爲何要求組件模板只能有一個根元素?」html

筆者入坑學習 Vue 也算是有段時間了,文檔和一些大佬的項目、文章也多少看了一些,可是彷佛這個問題思考的不多,每次在寫項目或是 demo 的時候直接就是代碼往上懟,深刻的地方也僅限於瞭解了路由和模板編譯的源碼實現,也沒有深究過爲何只能有一個根元素。這個問題我以爲對於剛入門 Vue 學習的朋友來講是一個很好的問題,在剛開始的時候就要知其因此然才能更好的掌握一門語言。前端

在 google 和掘金簡單找了一下,相關的問題資料彷佛不多,也少有人寫這個專題,那就很少吹了,今天就由我來帶你們分析一下這個問題。如下都是我本身查閱了一些資料以後的我的思考,若是你以爲有問題,歡迎在評論區指正和一塊兒探討~vue

Vue 實例

首先,我以爲這個問題要先從 Vue 的實例開始講起。Vue 的實例通常都是長成下面這個樣子,不一樣的只是 id 名的不一樣。webpack

<div id="app"></div>
複製代碼
var vm = new Vue({
    el: '#app',
    data: {},
    methods: {}
    ...
})
複製代碼

這就是 Vue 實例的基本結構,並不陌生。從這裏能夠看到,el 的指定是一個 id 爲 app 的 div 元素,Vue 實例接管了對它的控制,減小了咱們的 DOM 操做,須要被 vm 控制的元素所有加在它的內部。若是是須要控制不一樣的部分,這就須要多個 Vue 的實例來實現。疑問就來了,爲何須要不一樣的 Vue 實例來接管?git

入口問題

在 Vue 中指定 el 選項是給 Vue 實例指定一個 SPA 入口,有可能你的頁面會長成像下面這樣:github

<div id="app"></div>
<div id="app1"></div>
<div id="app2"></div>
複製代碼

Vue 實例其實並不知道哪個是入口,它應該接管哪個部分,因此你要給它指定一個惟一的元素做爲入口。每個入口能夠看做是一個 Vue 的類,Vue 要把這個入口進去的全部東西都取出來進行輪循渲染一遍,再把它從新掛載回頁面中的 DOM 裏面去。打給比方來講,一個 Vue 實例只擁有一個鑰匙,一個鑰匙只能開一把鎖,可是頁面上有不少把鎖,若是你不說清楚它是哪把鎖的鑰匙,Vue 實例就不知道接下來要怎麼作了。web

固然,這只是一個比較淺顯的理解。你可能會說,我指定幾個入口讓 Vue 實例去一個一個試就行了,咱們往下看。面試

虛擬 DOM

「虛擬 DOM」是咱們對由 Vue 組件樹創建起來的整個 VNode 樹的稱呼vue-cli

學習 Vue 不得不說的就是2.0引入的Virtual DOM,引入虛擬 DOM 後,在框架的內部將虛擬 DOM 樹型結構與真實 DOM 作了映射,讓咱們不用再命令式的去操做 DOM。

在聊這個話題以前,若是有對於虛擬 DOM 還不是特別瞭解的,推薦看這篇文章👉《詳解Vue中的虛擬DOM》

引用裏面的一張圖片:

虛擬 DOM 的渲染

從這張圖能夠看出來虛擬 DOM 的一個渲染過程,那咱們再回到本文的話題:爲何只能由一個根元素?
咱們來看一個例子,假設你的 Vue 實例接管的 DOM 結構長成這個樣子:

<div id="app">
    <h1 id="h3">My title</h1>
    <span>Content</span>
    Other text
    <!-- annotation text -->
</div>
複製代碼

它在瀏覽器內部的表現是一個這樣的 DOM 樹:

DOM樹

原諒我畫圖技術差,不過我想展現的效果達到了。從這能夠看出它是一個樹的結構,每一個元素、文字、註釋都是一個節點,虛擬 DOM 遵循的也是這樣的一個樹的數據結構。

回到正題,咱們的指定的 el 也就是整個 DOM 結構的根。如今就很好說了,咱們只有指定了惟一的 el 根元素,才能交給 Vue 實例在內部經過 createElement 方法生成一個對應的虛擬 DOM 結構映射真實的 DOM 元素進行操做渲染成真正的 HTML

換句話來講,能夠把 el 對應的元素理解成 Vue 接管部分中的一個頂級標籤,就像基本的 HTML 結構中,頂級標籤是 <html></html>,只能有一個這樣的標籤存在。對應到 Vue 中也是這樣,若是你給它兩個頂級標籤,那麼對應的 DOM 結構就沒法生成了,這也就解釋了以前的疑惑:爲何不能指定多個入口讓 Vue 實例一個個的試。

不知道我這樣的解釋有沒有說明白這個問題,若是沒清楚咱們下面再來看看。

vue-cli

如今實際的項目開發中,使用腳手架 vue-cli 開發居多,咱們來看看。

vue-cli 的形式是單文件組件,一個 .vue 頁面的基本結構是這樣的:

<template>
  <div></div>
</template>

<script>
export default {

}
</script>

<style>

</style>
複製代碼

在這裏,<template> 標籤下也只能有一個根元素 div,這是爲何?

在說這個話題以前,咱們須要瞭解 H5 新標籤 <template> 的一些特性,能夠參考文檔,它保證了內部的內容有效但不會被渲染。vue-cli 本質上是會把 .vue 文件經過 webpack 配置打包成一系列的 js/css 文件注入到一個 html 文件中交給瀏覽器進行解釋執行,咱們看一個打包好的文件目錄:

這也就是說,每一個 .vue 文件都會是一個 Vue 的實例,而 <template> 標籤中的內容就是 Vue 實例接管造成虛擬 DOM 的那部份內容。若是在 template 下有多個 div,那麼虛擬 DOM 樹就沒辦法生成了。

問題抽象

其實這個問題歸結到最後,也能夠抽象爲一個問題:爲何抽象出來的 DOM 樹只能有一個根?

  1. 從查找和遍歷的角度來講,若是有多個根,那麼咱們的查找和遍歷的效率會很低。
  2. 若是一個樹有多個根,說明能夠優化,確定會有一個節點是能夠訪問到全部的節點,那這個節點就會成爲新的根節點。
  3. 再從 Vue 自己來講,若是說一個組件有多個入口多個根,那不就意味着你的組件還能夠進一步拆分紅多個組件,進一步組件化,下降代碼之間的耦合程度。

結語

其實在咱們的學習過程當中,有不少問題都是咱們似懂非懂的,就是知其然不知其因此然。說白了就是知道輪子怎麼用,可是不知道輪子內部的運行機制,雖說不要去重複造輪子,但咱們至少要知道輪子是怎麼運行起來的。以上都是我我的對這個問題的一些理解,經過這篇文章但願能夠幫助你對這個問題有更深入的認識。若是你以爲有什麼不對的地方,歡迎直接指正,共同進步!

參考內容
《詳解Vue中的虛擬DOM》
《vue2.0的虛擬DOM渲染思路分析》
染陌同窗《說說VNode節點(Vue.js實現)》
官方文檔
官方API文檔
官方issue相關討論

我把個人學習記錄都記錄在了個人 github 而且會持續的更新下去,有興趣的小夥伴能夠看看~
github.com/tearill/Rea…

相關文章
相關標籤/搜索