Vue 進階系列(三)之Render函數原理及實現

(關注福利,關注本公衆號回覆[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導)
html

Vue進階系列彙總以下,歡迎閱讀,歡迎加高級前端進階羣一塊兒學習(文末)。前端

Vue 進階系列(一)之響應式原理及實現 vue

Vue 進階系列(二)之插件原理及實現node

Vue 進階系列(三)之Render函數原理及實現git

Render函數原理

根據第一篇文章介紹的響應式原理,以下圖所示。
imagegithub

在初始化階段,本質上發生在auto run函數中,而後經過render函數生成Virtual DOMview根據Virtual DOM生成Actual DOM。由於render函數依賴於頁面上全部的數據data,而且這些數據是響應式的,全部的數據做爲組件render函數的依賴。一旦這些數據有所改變,那麼render函數會被從新調用。面試

在更新階段,render函數會從新調用而且返回一個新的Virtual Dom,新舊Virtual DOM之間會進行比較,把diff以後的最小改動應用到Actual DOM中。編程

Watcher負責收集依賴,清除依賴和通知依賴。在大型複雜的組件樹結構下,因爲採用了精確的依賴追蹤系統,因此會避免組件的過分渲染。瀏覽器

Actual DOM 和 Virtual DOM

Actual DOM 經過document.createElement('div')生成一個DOM節點。性能優化

document.createElement('div')

// 瀏覽器原生對象(開銷大)
"[object HTMLDivElement]"

Virtual DOM 經過 vm.$createElement('div')生成一個JS對象,VDOM對象有一個表示div的tag屬性,有一個包含了全部可能特性的data屬性,可能還有一個包含更多虛擬節點的children列表。

vm.$createElement('div')

// 純JS對象(輕量)
{ tag: 'div', data: { attrs: {}, ...}, children: [] }

由於Virtual DOM的渲染邏輯和Actual DOM解耦了,因此有能力運行在的非瀏覽器環境中,這就是爲何Virtual DOM出現以後混合開發開始流行的緣由,React Native 和 Weex可以實現的原理就是這個。

JSX和Template

JSX和Template都是用於聲明DOM和state之間關係的一種方式,在Vue中,Template是默認推薦的方式,可是也可使用JSX來作更靈活的事。

JSX更加動態化,對於使用編程語言是頗有幫助的,能夠作任何事,可是動態化使得編譯優化更加複雜和困難。

Template更加靜態化而且對於表達式有更多約束,可是能夠快速複用已經存在的模板,模板約束意味着能夠在編譯時作更多的性能優化,相對於JSX在編譯時間上有着更多優點。

實例1:實現example組件

要求使用以下

<example :tags="['h1', 'h2', 'h3']"></example>

要求輸出以下

<div>
  <h1>0</h1>
  <h2>1</h2>
  <h3>2</h3>
</div>

上面這個需求能夠經過render函數來作,官方提供了createElement 函數用來生成模板。createElement('div', {}, [...])可接受的參數以下。

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一個 HTML 標籤字符串,組件選項對象,或者
  // 解析上述任何一種的一個 async 異步函數。必需參數。
  'div',

  // {Object}
  // 一個包含模板相關屬性的數據對象
  // 你能夠在 template 中使用這些特性。可選參數。
  {
    
  },

  // {String | Array}
  // 子虛擬節點 (VNodes),由 `createElement()` 構建而成,
  // 也可使用字符串來生成「文本虛擬節點」。可選參數。
  [
    '先寫一些文字',
    createElement('h1', '一則頭條'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

知道了用法以後,就能夠在render中返回createElement生成的虛擬節點,外層是div,內層是三個錨點標題h1 h2 h3,因此內層須要遍歷下,使用兩個createElement就能夠完成了。

一般使用h做爲createElement的別名,這是Vue的通用慣例,也是JSX的要求。

實現以下

<!--引用-->
<script src="../node_modules/vue/dist/vue.js"></script>

<!--定義template -->
<div id="app">
  <example :tags="['h1', 'h2', 'h3']"></example>
</div>

<script>
    // 定義example組件
    Vue.component('example', {
      props: ['tags'],
      render (h) {
        
        // 第二個參數是一個包含模板相關屬性的數據對象,可選參數
        
        // 子虛擬節點(VNodes)參數能夠傳入字符串或者數字,
        // 經過createElement生成,可選參數
        return h('div', this.tags.map((tag, i) => h(tag, i)))
      }
    })
    
    // 實例化
    new Vue({ el: '#app' })
</script>

實例2:實現動態的<example>組件

要求以下

  • 實現一個Foo組件渲染<div>foo</div>,實現一個Bar組件渲染<div>bar</div>
  • 實現一個<example>組件,根據屬性ok動態渲染Foo組件或者Bar組件。若是屬性oktrue,那麼最終的渲染應該是<div>foo</div>
  • 實現一個按鈕控制屬性ok,經過這個屬性讓<example>Foo或者Bar之間切換。

根據上面的要求,在模板中調用<example>組件,而後定義<button>組件,同時綁定屬性ok

實現以下

<!--引用-->
<script src="../node_modules/vue/dist/vue.js"></script>

<!--定義template -->
<div id="app">

  <!--綁定屬性ok-->
  <example :ok="ok"></example>
  
  <!--綁定點擊事件-->
  <button @click="ok = !ok">toggle</button>
</div>

<script>
    // 定義Foo
    const Foo = {
      render (h) {
        return h('div', 'foo')
      }
    }
    
    // 定義Bar
    const Bar = {
      render (h) {
        return h('div', 'bar')
      }
    }
    
    // 定義example組件
    // 根據ok屬性動態切換
    Vue.component('example', {
      props: ['ok'],
      render (h) {
        return h(this.ok ? Foo : Bar)
      }
    })
    
    // 實例化
    new Vue({
      el: '#app',
      data: { ok: true }
    })
</script>

實例3:實現組件

要求以下

  • 實現一個withAvatarURL函數,要求傳入一個帶有url屬性的組件,返回一個接收username屬性的高階組件,這個高階組件主要負責獲取相應的頭像URL。
  • 在API返回以前,高階組件將佔位符URLhttp://via.placeholder.com/200x200傳遞給內部組件。

例子以下

const SmartAvatar = withAvatarURL(Avatar)

// 使用這個方式
<smart-avatar username="vuejs"></smart-avatar>

// 替換下面的方式
<avatar url="/path/to/image.png"></avatar>

withAvatarURL函數返回一個對象,接收username屬性,在生命週期created獲取頭像URL。Avatar對象接收src屬性,src的內容從withAvatarURL中獲取,而後展現在<img>上。實例化的時候,傳入新定義的組件名SmartAvatar

實現以下

<!--引用-->
<script src="../node_modules/vue/dist/vue.js"></script>

<!--定義template-->
<div id="app">
  <smart-avatar username="vuejs"></smart-avatar>
</div>

<script>
    // 獲取頭像URL
    function fetchURL (username, cb) {
      setTimeout(() => {
        // 獲取頭像並回傳
        cb('https://avatars3.githubusercontent.com/u/6128107?v=4&s=200')
      }, 500)
    }
    
    // 傳遞的InnerComponent
    const Avatar = {
      props: ['src'],
      template: `<img :src="src">`
    }
    
    function withAvatarURL (InnerComponent) {
      return {
        props: ['username'],
        inheritAttrs: false, // 2.4 only,組件將不會把未被註冊的props呈現爲普通的HTML屬性
        data () {
          return { url: null }
        },
        created () {
          // 獲取頭像URL並回傳給this.url
          fetchURL(this.username, url => {
            this.url = url
          })
        },
        render (h) {
          return h(InnerComponent, {
            attrs: this.$attrs, // 2.4 only,獲取到沒有使用的註冊屬性
            props: {
              src: this.url || 'http://via.placeholder.com/200x200'
            }
          })
        }
      }
    }
    
    const SmartAvatar = withAvatarURL(Avatar)
    
    // 實例化,新構造組件名爲SmartAvatar或smart-avatar
    new Vue({
      el: '#app',
      components: { SmartAvatar }
    })
</script>
本文內容參考自VUE做者尤大的付費視頻
Vue官網之渲染函數 & JSX

交流

本人Github連接以下,歡迎各位Star

http://github.com/yygmind/blog

我是木易楊,網易高級前端工程師,跟着我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高級前端的世界,在進階的路上,共勉!

若是你想加羣討論每期面試知識點,公衆號回覆[加羣]便可

相關文章
相關標籤/搜索