數(shù)據(jù)驅動渲染(二)

回憶

????????這里我們將對render函數(shù)把template轉化成vnode的過程進行介紹。

????????Vue.prototype._render方法中調用vm.$options.render.call(vm_renderProxy, vm.$creatElement),$options.render本身是個函數(shù),以creatElement方法為參數(shù)。傳入的參數(shù)vm.$creatElement是個function createElement(vm,a,x,c,d,true)函數(shù)。createElement最終會調用??_createElement。

????????轉化的最終方法為_createElement(context(VNode 的上下文環(huán)境,它是?Component?類型),tag(標簽,它可以是一個字符串,也可以是一個?Component),data(VNode 的數(shù)據(jù),?VNodeData?類型),children(當前 VNode 的子節(jié)點,它是任意類型的,它接下來需要被規(guī)范為標準的 VNode 數(shù)組),normalizationType(子節(jié)點規(guī)范的類型,類型不同規(guī)范的方法也就不一樣,它主要是參考?render?函數(shù)是編譯生成的還是用戶手寫的))。

????????主要邏輯分為兩個部分?children?的規(guī)范化以及 VNode 的創(chuàng)建

? ?????1、?children?的規(guī)范化,會調用simpleNormalizeChildren或者normalizeChildren把children由樹狀結構打平成一維數(shù)組。2、通過vnode=new VNode()創(chuàng)建vnode。

render

core/instance/lifecyle.js

????????Vue 的?_render?方法是實例的一個私有方法,它用來把實例渲染成一個虛擬 Node。它的定義在src/core/instance/render.js文件中。

1、wm._renderProxy在生產環(huán)境就是vm(this本身),在生產環(huán)境是proxy對象。2、vm.$creatElement是在initRender函數(shù)中定義(在init初始化時會執(zhí)行)。

initRender函數(shù)主要部分($creatElement)

對手寫render和編譯生成render分別執(zhí)行creatElement()。手寫render函數(shù)和直接在template函數(shù)寫編譯出來render,渲染過程不一樣。1、直接在template函數(shù)寫的話會在執(zhí)行newVue之前先把template內容渲染出來,再執(zhí)行newVue()的mounted時把template內的插值{{...}}替換成真實數(shù)據(jù)。2、直接通過純render函數(shù),不用在頁面上顯示插值{{..}},而在render函數(shù)執(zhí)行完畢后,直接把message寫上去,也不會再執(zhí)行把template轉化成render函數(shù)這一步,用戶體驗更好。

? ??????最關鍵的是?render?方法的調用,我們在平時的開發(fā)工作中手寫?render?方法的場景比較少,而寫的比較多的是?template?模板,在之前的?mounted?方法的實現(xiàn)中,會把?template?編譯成?render?方法,但這個編譯過程是非常復雜的,我們不打算在這里展開講,之后會專門花一個章節(jié)來分析 Vue 的編譯過程。

????????可以看到,render?函數(shù)中的?createElement?方法就是?vm.$createElement?方法。

????????實際上,vm.$createElement?方法定義是在執(zhí)行?initRender?方法的時候,可以看到除了?vm.$createElement方法,還有一個?vm._c?方法,它是被模板編譯成的?render?函數(shù)使用,而vm.$createElement?是用戶手寫?render?方法使用的, 這倆個方法支持的參數(shù)相同,并且內部都調用了?createElement?方法。

render渲染實際步驟

? ???????在 Vue 的官方文檔中介紹了?render?函數(shù)的第一個參數(shù)是?createElement,那么結合之前的例子。

template

相當于我們編寫如下?render?函數(shù):

render函數(shù)

再回到?_render?函數(shù)中的?render?方法的調用:? ? ? ??

調用

繼續(xù)看看vm._renderProxy,定義在instance/init.js中

我們繼續(xù)往下找,instance/proxy中

做對象訪問劫持,如果沒取到就報錯

????????看完render渲染函數(shù) vnode = render.call(vm._renderProxy, vm.$createElement),我們接著往下看。

判斷dom根節(jié)點是否只有一個Vnode,否就報錯。最后返回一個Vnode

????????vm._render?最終是通過執(zhí)行?createElement?方法并返回的是?vnode,它是一個虛擬 Node。Vue 2.0 相比 Vue 1.0 最大的升級就是利用了 Virtual DOM。因此在分析?createElement?的實現(xiàn)前,我們先了解一下 Virtual DOM 的概念。

Virtual DOM

????????Virtual DOM 這個概念相信大部分人都不會陌生,它產生的前提是瀏覽器中的 DOM 是很“昂貴"的,為了更直觀的感受,可以簡單的把一個簡單的 div 元素的屬性都打印出來,很多屬性非常龐大,因為瀏覽器的標準就把 DOM 設計的非常復雜。當我們頻繁的去做 DOM 更新,會產生一定的性能問題。

????????而 Virtual DOM 就是用一個原生的 JS 對象去描述一個 DOM 節(jié)點,所以它比創(chuàng)建一個 DOM 的代價要小很多。在 Vue.js 中,Virtual DOM 是用?VNode?這么一個 Class 去描述,它是定義在src/core/vdom/vnode.js中的。

????????Vue.js 中的 Virtual DOM 的定義還是略微復雜一些的,因為它這里包含了很多 Vue.js 的特性。這里千萬不要被這些茫茫多的屬性嚇到,實際上 Vue.js 中 Virtual DOM 是借鑒了一個開源庫snabbdom的實現(xiàn),然后加入了一些 Vue.js 特色的東西。我建議大家如果想深入了解 Vue.js 的 Virtual DOM 前不妨先閱讀這個庫的源碼,因為它更加簡單和純粹。

? ??????其實 VNode 是對真實 DOM 的一種抽象描述,它的核心定義無非就幾個關鍵屬性,標簽名、數(shù)據(jù)、子節(jié)點、鍵值等,其它屬性都是都是用來擴展 VNode 的靈活性以及實現(xiàn)一些特殊 feature 的。由于 VNode 只是用來映射到真實 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常輕量和簡單的。

????????Virtual DOM 除了它的數(shù)據(jù)結構的定義,映射到真實的 DOM 實際上要經歷 VNode 的 create、diff、patch 等過程。那么在 Vue.js 中,VNode 的 create?是通過之前提到的?createElement?方法創(chuàng)建的,我們接下來分析這部分的實現(xiàn)。

createElement

????????接下來分析這個creatElement, Vue.js 利用 createElement 方法創(chuàng)建 VNode,它定義在src/core/vdom/create-elemenet.js中。

參數(shù)

????????_createElement?方法有 5 個參數(shù),context?表示 VNode 的上下文環(huán)境,它是?Component?類型;tag?表示標簽,它可以是一個字符串,也可以是一個?Component;data?表示 VNode 的數(shù)據(jù),它是一個?VNodeData?類型,可以在?flow/vnode.js?中找到它的定義,這里先不展開說;children表示當前 VNode 的子節(jié)點,它是任意類型的,它接下來需要被規(guī)范為標準的 VNode 數(shù)組;normalizationType表示子節(jié)點規(guī)范的類型,類型不同規(guī)范的方法也就不一樣,它主要是參考?render?函數(shù)是編譯生成的還是用戶手寫的。

????????createElement?函數(shù)的流程略微有點多,我們接下來主要分析 2 個重點的流程 ——?children?的規(guī)范化以及 VNode 的創(chuàng)建

children規(guī)范化
規(guī)范化詳細
遞歸打平children

children 的規(guī)范化

????????由于 Virtual DOM 實際上是一個樹狀結構,每一個 VNode 可能會有若干個子節(jié)點,這些子節(jié)點應該也是 VNode 的類型。_createElement?接收的第 4 個參數(shù) children 是任意類型的,因此我們需要把它們規(guī)范成 VNode 類型。

????????這里根據(jù)?normalizationType?的不同,調用了?normalizeChildren(children)?和?simpleNormalizeChildren(children)?方法,它們的定義都在src/core/vdom/helpers/normalzie-children.js?

? ??????simpleNormalizeChildren?方法調用場景是?render?函數(shù)當函數(shù)是編譯生成的。理論上編譯生成的?children?都已經是 VNode 類型的,但這里有一個例外就是?functional component?函數(shù)式組件返回的是一個數(shù)組而不是一個根節(jié)點,所以會通過?Array.prototype.concat?方法把整個?children?數(shù)組打平,讓它的深度只有一層。

? ???????normalizeChildren方法的調用場景有 2 種,一個場景是?render?函數(shù)是用戶手寫的,當?children?只有一個節(jié)點的時候,Vue.js 從接口層面允許用戶把?children?寫成基礎類型用來創(chuàng)建單個簡單的文本節(jié)點,這種情況會調用?createTextVNode?創(chuàng)建一個文本節(jié)點的 VNode;另一個場景是當編譯?slot、v-for?的時候會產生嵌套數(shù)組的情況,會調用?normalizeArrayChildren?方法,接下來看一下它的實現(xiàn)。

? ??????normalizeArrayChildren接收 2 個參數(shù),children?表示要規(guī)范的子節(jié)點nestedIndex?表示嵌套的索引,因為單個?child?可能是一個數(shù)組類型。?normalizeArrayChildren?主要的邏輯就是遍歷?children,獲得單個節(jié)點?c,然后對?c?的類型判斷,如果是一個數(shù)組類型,則遞歸調用?normalizeArrayChildren; 如果是基礎類型,則通過?createTextVNode?方法轉換成 VNode 類型;否則就已經是 VNode 類型了,如果?children?是一個列表并且列表還存在嵌套的情況,則根據(jù)?nestedIndex?去更新它的 key。這里需要注意一點,在遍歷的過程中,對這 3 種情況都做了如下處理:如果存在兩個連續(xù)的?text?節(jié)點,會把它們合并成一個?text?節(jié)點。

? ??????經過對?children?的規(guī)范化,children?變成了一個類型為 VNode 的 Array。

創(chuàng)建Vnode

VNode 的創(chuàng)建

????????回到?createElement?函數(shù),規(guī)范化?children?后,接下來會去創(chuàng)建一個 VNode 的實例。

????????這里先對?tag?做判斷,如果是?string?類型,則接著判斷如果是內置的一些節(jié)點,則直接創(chuàng)建一個普通 VNode,如果是為已注冊的組件名,則通過?createComponent?創(chuàng)建一個組件類型的 VNode,否則創(chuàng)建一個未知的標簽的 VNode。 如果是?tag?一個?Component?類型,則直接調用?createComponent?創(chuàng)建一個組件類型的 VNode 節(jié)點。對于?createComponent?創(chuàng)建組件類型的 VNode 的過程,我們之后會去介紹,本質上它還是返回了一個 VNode。

? ??????那么至此,我們大致了解了?createElement?創(chuàng)建 VNode 的過程,每個 VNode 有?children,children?每個元素也是一個 VNode,這樣就形成了一個 VNode Tree,它很好的描述了我們的 DOM Tree。

????????回到?mountComponent?函數(shù)的過程,我們已經知道?vm._render?是如何創(chuàng)建了一個 VNode,接下來就是要把這個 VNode 渲染成一個真實的 DOM 并渲染出來,這個過程是通過?vm._update?完成的,接下來分析一下這個過程。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 回憶 首先,render函數(shù)中手寫h=>h(app),new Vue()實例初始化init()和原來一樣。$mou...
    LoveBugs_King閱讀 2,303評論 1 2
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內容,還有我對于 Vue 1.0 印象不深的內容。關于...
    云之外閱讀 5,074評論 0 29
  • 一、Native 每一個頁面都是一個 instance,framework 是全局唯一的,js 和 native ...
    Yang152412閱讀 5,698評論 0 50
  • 這個介紹的是 createElement 是怎么創(chuàng)建一個元素的。接下來還有一篇會介紹到 createElement...
    阿暢_閱讀 23,498評論 0 3
  • 今天,還是那個我還是那個循規(guī)蹈矩。 今天我又和穆蕊蕊,還有他的奶奶一起去了鄉(xiāng)下。 ...
    谷殿艷閱讀 99評論 0 0