高級 Vue 組件模式 (9)

  • 2020 年 1 月 21 日
  • 筆記

09 使用 Functional 組件

目標

到此為止,我們的 toggle 組件已經足夠強大以及好用了,因此這篇文章不會再為它增加新的特性。如果你是從第一篇文章一直讀到這裡的讀者,你一定會發現,整篇文章中,我幾乎沒有對 toggle-on 和 toggle-off 做出任何更改和重構,因此這篇文章著重來重構一下它們。

之前一直沒有對它們進行任何更改,一個很重要的原因是因為它們足夠簡單,組件內部沒有任何 data 狀態,僅靠外部傳入的 prop 屬性 on 來決定內部渲染邏輯。這聽起來似乎有些耳熟啊,沒錯,它們就是在上一篇文章中所提及的木偶組件(Dump Component)。在 Vue 中,這種類型的組件也可以叫做函數式組件(Functional Component)。

仔細觀察 app 組件的模板程式碼,會發現存在一定的冗餘性的,比如:

<toggle-on :on="status.on">{{firstTimes}}</toggle-on>  <toggle-off :on="status.on">{{firstTimes}}</toggle-off>

這裡兩行程式碼的邏輯幾乎一模一樣,但我們卻要寫兩次。同時你還會發現一個問題,由於其內部的渲染邏輯是通過 v-if 來描述的,實際上在 Vue 渲染完成後,會渲染兩個 dom 節點,在切換時的狀態從 devtool 中觀察的效果大概是這樣子的:

未顯示的節點是一個注釋節點,而顯示的節點是一個 div 節點。

這篇文章將著重解決這兩個問題:

  • 將 toggle-on 和 toggle-off 合二為一,減少程式碼冗餘性
  • 重構以 v-if 實現的渲染邏輯,改為更好的動態渲染邏輯(僅使用一個 dom 節點)

實現

轉化為函數式組件

首先,先將已經存在的 toggle-on 和 toggle-off 組件轉化為函數式組件,很簡單,只需保留 template 程式碼塊即可,同時在左邊的標籤上聲明 functional 屬性,或者在 script 程式碼塊中聲明 functional: true 也是可以的。唯一要注意的是,由於函數式組件沒有 data 也沒有 this,因此所有模板中用到的與 prop 相關的渲染邏輯,都要作出相應更改,比如原先的 on 要改為 props.on的形式,由於這裡我們要移除 v-if 的渲染邏輯,因此直接移除即可,詳細程式碼如下:

// ToggleOn.vue  <template functional>    <div class="toggle-on"><slot></slot></div>  </template>    <style>  .toggle-on {    color: #86d993;  }  </style>    // ToggleOff.vue  <template functional>    <div class="toggle-off"><slot></slot></div>  </template>    <style>  .toggle-off {    color: red;  }  </style>

除此之外,還可以發現,我為兩個組件增加不同的顏色樣式以便於區分當前的開關狀態。

實現 ToggleStatus 組件

接下來實現今天的主角,ToggleStatus 組件,由於我們的目標是將原先的二個函數式組件合二為一,因此這個組件本身應當也是一個函數式組件,不過我們需要使用另外一種寫法,如下:

<script>  import ToggleOn from './ToggleOn'  import ToggleOff from './ToggleOff'    export default {    functional: true,    render(createElement, {props, data, children}) {      let Comp = ToggleOff        if(props.on) Comp = ToggleOn        return createElement(Comp, data, children)    }  }  </script>

關於這種寫法中,render 和 createElement 方法的參數就不贅述了,不熟悉的讀者請參考官方文檔。可以發現,這裡將 toggle-on 和 toggle-off 以模組的形式導入,之後由 props.on 的值進行判定,從而決定哪一個被作為 createElement 方法的第一個參數進行渲染。

諸如 data 和 children 參數我們原封不動的傳入 createElement 即可,因為這裡對於 toggle-status 組件的定位是一個代理組件,對於其他參數以及子元素應當原封不動的傳遞給被代理的組件中。

之後在 app 組件中更改響應的渲染邏輯即可,如下:

// controlled toggle  <toggle-status :on="status.on">{{firstTimes}}</toggle-status>    // uncontrolled toggle  <toggle-status :on="status.on">{{secondTimes}}</toggle-status>

成果

一切如原先一樣,只不過這次我們可以少寫一行冗餘的程式碼了。同時打開 devtool 可以發現,兩種狀態的組件會復用同一個 dom 節點,如下:

你可以通過下面的鏈接來看看這個組件的實現程式碼以及演示:

總結

關於函數式組件,我是在 React 中第一次接觸,其形式和它的名字一樣,就是一個函數。這種組件和普通組件相比的優勢主要在於,它是無狀態的,這意味著它的可測試性和可讀性更好,同時一些情況下,渲染開銷也更小。

我們在日常工作中,可能會經常遇到動態渲染的需求,一般情況下,我們均會通過 v-if 來解決,在比較簡單的情況下,v-if 確實一種很簡單且高效的方式,但是隨著組件複雜度的上升,很可能會出現麵條式的程式碼,可讀性和可測試性都大打折扣,這是不妨換一個角度從渲染機制本身將組件重構為更小的顆粒,並用一個函數式組件動態的代理它們,可能會得到更好的效果,舉一個比較常見的例子,比如表單系統中的表單項,一般都具有多種渲染狀態,如編輯狀態、瀏覽狀態、禁用狀態等等,這時利用該模式來抽離不同狀態的渲染邏輯就非常不錯。

Tags: