詳解 Weex 頁面的渲染過程

這篇文章介紹了一個 Weex 頁面的渲染過程,涉及不少框架內部的細節。html

「喲」

圖片描述

在線例子,使用 Weex Playground 掃碼便可預覽。前端

這是一個使用 Vue.js 2.x 語法寫的一個小例子,極其簡單,就一個字,能夠藉助 Weex 在移動端中渲染生成原生組件。vue

這也是實現文字水平垂直居中的最簡例子。node

源代碼

組件代碼:git

<!-- yo.vue -->
<template>
  <div style="justify-content:center;">
    <text class="freestyle">喲</text>
  </div>
</template>

<style scoped>
  .freestyle {
    text-align: center;
    font-size: 200px;
  }
</style>

除了組件代碼之外,還須要一個入口文件指定掛載點並觸發渲染:github

// entry.js
import Vue from 'vue'
import Yo from 'yo.vue'

Yo.el = '#root'
new Vue(Yo)

編譯

.vue 文件是沒法被直接執行的,必需要編譯成 .js 格式的文件才能夠被 Web 或 Weex 平臺執行。web

.vue 文件一般能夠分爲三部分:<template><style><script><template> 是必需要有的,其餘可選。其中 <script> 中的代碼會保留或者被轉換成 ES5 的語法;<style> 中的 CSS 在 Weex 平臺上會被轉換成 JSON 格式的樣式聲明,放到組件的定義中去;<template> 會被編譯生成組件定義中 render 函數,能夠理解爲 render 函數的語法糖。apache

上述例子真實生成的代碼是這樣的,比較亂,把模塊解開將其簡化一下,和下邊的代碼等價:api

// { "framework": "Vue" }

new Vue({
  el: '#root',
  style: {
    freestyle: {
      textAlign: 'center',
      fontSize: 200
    }
  },
  render: function (h) {
    return h(
      'div',
      { staticStyle: { justifyContent: 'center' } },
      [h(
        'text',
        { staticClass: ['freestyle'] },
        ['喲']
      )]
    )
  }
})

執行

初始化執行環境

要想在移動端上執行上述代碼,就須要集成 Weex SDK。瀏覽器

在應用啓動時就會初始化 Weex SDK,準備好執行環境,而後能夠從網絡或者本地加載打包好的 js 文件,調用 SDK 提供的 render 或者 renderWithURL 方法啓動渲染。

圖片描述

圖中畫出了 Weex SDK 的部份內容。其中 weex-vue-frameworkVue.js 是對等的,語法和內部機制都是同樣的,只不過 Vue.js 最終建立的是 DOM 元素,而 weex-vue-framework 則是向原生端發送渲染指令,最終渲染生成的是原生組件。Weex Runtime 用來對接上層前端框架(如 Vue.js 和 Rax)而且負責和原生端之間的通訊。Render Engine 就是針對各個端開發的原生渲染器,包含了 Weex 內置組件和模塊的實現,可擴展。

在 Weex SDK 中也含有 weex-rax-framework,支持使用 Rax 做爲其上層前端框架。 這個例子使用的是 Vue 2.0 的語法,爲了簡潔只畫出了 weex-vue-framework。

建立組件

Weex 接收到 js 文件之後,會先檢查它的格式,發現用的是 Vue 版本,就會調用 weex-vue-framework 中提供的 createInstance 方法建立實例。

代碼裏 new Vue() 會建立一個組件,經過其 render 函數建立 VNode 節點,而且觸發相應的生命週期,若是指定了 el 屬性也會執行掛載(mount),根據 Virtual DOM 在指定平臺中生成真實的 UI 組件。

上述代碼只有一個組件兩個標籤和一些簡單樣式,最終生成的 VNode 節點以下(數據結構有簡化):

{
  tag: 'div',
  data: {
    staticStyle: { justifyContent: 'center' }
  },
  children: [{
    tag: 'text',
    data: {
      staticClass: 'freestyle'
    },
    context: {
      $options: {
        style: {
          freestyle: {
            textAlign: 'center',
            fontSize: 200
          }
        }
      }
    },
    children: [{
      tag: '',
      text: '喲'
    }]
  }]
}

Patch

再生成了 VNode 節點以後,還須要執行 「patch」 將虛擬 DOM 繪製成真實的 UI。在執行 patch 以前的過程都是 Web 和 Weex 通用的,因此文件格式、打包編譯過程、模板指令、組件的生命週期、數據綁定等上層語法都是一致的。

然而因爲目標執行環境不一樣(瀏覽器和 Weex 容器),在渲染真實 UI 的時候調用的接口也不一樣。

圖片描述

在 Vue.js 內部,Web 平臺和 Weex 平臺中的 patch 方法是不一樣的,可是都是由 createPatchFunction 這個方法生成的,它支持傳遞 nodeOps 參數,在其中代理了全部 DOM 操做。在 Web 平臺中 nodeOps 背後調用的都是 Web API,在 Weex 平臺中則調用的是 Weex Runtime 提供的 Native DOM API。觸發 DOM 渲染的入口一致,可是不一樣平臺的實現方式不一樣。

例如 nodeOps 中的 createElement 的操做,在 Web 平臺中實際調用的是 document.createElement(tagName) 這個接口(參考代碼);而在 Weex 平臺中實際執行的是 new renderer.Element(tagName)參考代碼)。

發送渲染指令

上述頁面的 patch 過程不只限於 Vue,在 Rax 中也調用了 Weex 的 Native DOM API,實現原理是一致的。發送渲染指令的過程是全部上層前端框架通用的,上層使用 Vue 仍是 Rax 對於原生渲染器而言是透明的,只是語法和構建 Virtual DOM 的方式有差別而已。

在上層前端框架調用了 Weex 平臺提供的 Native DOM API 以後,Weex Runtime 會構建一個用於渲染的節點樹,並將操做轉換成渲染指令發送給客戶端。

回顧文中提到的 「喲」 例子,上層框架調用了 Weex Runtime 中 createBodycreateElementappendChild 這三個接口,簡單構建了一個用於渲染的節點樹,最終生成了兩條渲染指令。

圖片描述

這些都屬於 Weex SDK 內部的底層細節,上層應用的開發者,乃至前端框架開發者都不須要了解此格式,並且在迭代過程當中極可能還會有調整。

圖中的 Platform API 指的是原生環境提供的 API,這些 API 是 Weex SDK 中原生模塊提供的,不是 js 中方法,也不是瀏覽器中的接口,是 Weex 內部不一樣模塊之間的約定。

目前來講渲染指令是基於 JSON 描述的,具體格式大體以下所示:

{
  module: 'dom',
  method: 'createBody',
  args: [{
    ref: '_root',
    type: 'div',
    style: { justifyContent: 'center' }
  }]
}
{
  module: 'dom',
  method: 'addElement',
  args: ['_root', {
    ref: '2',
    type: 'text',
    attr: { value: '喲' },
    style: { textAlign: 'center', fontSize: 200 }
  }]
}

渲染原生組件

原生渲染器接收上層傳來的渲染指令,而且逐步將其渲染成原生組件。

渲染指令分不少類,文章中提到的兩個都是用來建立節點的,其餘還有 moveElementupdateAttrsaddEvent 等各類指令。原生渲染器先是解析渲染指令的描述,而後分發給不一樣的模塊。關於 UI 繪製的指令都屬於 "dom" 模塊中,在 SDK 內部有組件的實現,其餘還有一些無界面的功能模塊,如 stream 、navigator 等模塊,也能夠經過發送指令的方式調用。

圖片描述

這個例子裏,第一個 createBody 的指令就建立了一個 <div> 的原生組件,同時也將樣式應用到了改組件上。第二個 addElement 指令向 <div> 中添加一個 <text> 組件,同時也聲明瞭組件的樣式和屬性值。

上述過程不是分階段一個一個執行的,而是能夠實現「流式」渲染的,有可能第一個 <div> 的原生組件還沒渲染好,<text> 的渲染指令又發過來了。當一個頁面特別大時,能看到一塊一塊的內容逐漸渲染出來的過程。

總結

沒啥可總結的,都是細節,並且是框架內部的細節,之後極可能還會變,對於如何寫好 Weex 的代碼沒有半毛錢幫助。

相關文章
相關標籤/搜索