高階 Vue 元件模式 (7)

HaoliangWu發表於2018-10-26

07 使用 State Initializers

目標

到目前為止,僅從 toggle 元件自身的角度來看,它已經可以滿足大多數的業務場景了。但我們會發現一個問題,就是當前 toggle 元件的狀態對於呼叫者來說,完全是黑盒狀態,即呼叫者無法初始化,也無法更改元件的開關狀態,這在一些場景無法滿足需求。

對於無法初始化開關狀態的問題,倒是很好解決,我們可以在 toggle 元件宣告一個 prop 屬性 on 來代表元件的預設開關狀態,同時在 mounted 生命週期函式中將這個預設值同步到元件 data 相應的屬性中去。

對於無法更改開關狀態的問題,似乎無法簡單通過宣告一個 prop 屬性的方式來解決,並且如果我們期望的更改邏輯是非同步的話,同樣無法滿足。

因此這篇文章著重來解決這兩個問題:

  • toggle 元件能夠支援開關狀態的初始化功能
  • toggle 元件能夠提供一個 reset 方法以供重置開關狀態
  • 重置開關狀態可以以非同步的方式進行

實現

初始化開關狀態

為了使 toggle 元件能夠支援預設狀態的傳入,我們採用宣告 prop 屬性的方式,如下:

on: {
  type: Boolean,
  default: false
}
複製程式碼

之後在其 mounted 生命週期對開關狀態進行同步,如下:

mounted() {
    this.status.on = this.on;
  }
複製程式碼

這樣當我們期望 toggle的狀態進行渲染時,可以這樣呼叫元件:

<toggle :on="true" @toggle="onToggle">
  ...
</toggle>
複製程式碼

重置開關狀態

為了能夠從外部更改 toggle 元件的開關狀態,我們可以在元件內部宣告一個觀測 on prop 屬性的監聽器,比如:

watch: {
  on(val){
    // do something...
  }
}
複製程式碼

但如果這麼做,會存在一個問題,即目標中關於開關狀態的更改邏輯的編寫者是元件呼叫者,而 watch 函式的編寫者是元件實現者,由於實現者無法預知呼叫者更改狀態的邏輯,所以使用 watch 是無法滿足條件的。

讓我們換一個角度來思考問題,既然實現者無法預知呼叫者的邏輯,何不把重置開關狀態的邏輯全部交由呼叫者來實現?別忘了 Vue 元件也是可以傳入 Function 型別的 prop 屬性的,如下:

onReset: {
  type: Function,
  default: () => this.on
},
複製程式碼

這樣就將提供重置狀態的邏輯暴露給了元件呼叫者,當然,如果呼叫者沒有提供相關重置邏輯,元件內部會自動降級為使用 on 屬性來作為重置的狀態值。

元件內部額外宣告一個 reset 方法,在其內部重置當前的開關狀態,如下:

reset(){
  this.status.on = this.onReset(this.status.on)
  this.$emit("reset", this.status.on)
}
複製程式碼

這裡會首先以當前開關狀態為引數,呼叫 onReset 方法,再將返回值賦值給當前狀態,並觸發一個 reset 事件以供父元件訂閱。

之後在 app 元件中,可以按如下方式傳入 onReset 函式,並編寫具體的重置邏輯:

// template
<toggle :on="false" @toggle="onToggle" :on-reset="resetToTrue">
...
</toggle>

// script
...
resetToTrue(on) {
  return true;
},
...
複製程式碼

執行效果如下:

高階 Vue 元件模式 (7)

支援非同步重置

在實現同步重置的基礎上,實現非同步重置十分簡單,通常情況下,處理非同步較好的方式是使用 Promise,使用 callback 也可以,使用 Observable 也是不錯的選擇,這裡我們選擇 Promise。

由於要同時處理同步和非同步兩種情況,只需把同步情況視為非同步情況即可,比如以下兩種情況在效果上是等價的:

// sync
this.status.on = this.onReset(this.status.on)

// async
Promise.resolve(this.onReset(this.status.on))
    .then(on => {
      this.status.on = on
    })
複製程式碼

onReset 函式如果返回的是一個 Promise 例項的話,Promise.resolve 也會正確解析到當它為 fullfill 狀態的值,這樣關於 reset 方法我們改版如下:

reset(){
  Promise.resolve(this.onReset(this.status.on))
    .then(on => {
      this.status.on = on
      this.$emit("reset", this.status.on)
    })
}
複製程式碼

在 app 元件中,可以傳入一個非同步的重置邏輯,這裡就不貼程式碼了,直接上一個執行截圖,元件會在點選重置按鈕後 1 秒後,重置為狀態:

高階 Vue 元件模式 (7)

成果

你可以下面的連結來看看這個元件的實現程式碼以及演示:

總結

Function 型別的 prop 屬性在一些情況下非常有用,比如文章中提及的狀態初始化,這其實是工廠模式的一種體現,在其他的框架中也有體現,比如 React 中,HOC 中提及的 render props 就是一種比較具體的應用,Angular 在宣告具有迴圈依賴的 Module 時,可以通過 () => Module 的方式進行宣告等等。

目錄

github gist

相關文章