高階 Vue 元件模式 (8)

weixin_34253539發表於2018-10-28

08 使用 Control Props

目標

在第七篇文章中,我們對 toggle 元件進行了重構,使父元件能夠傳入開關狀態的初始值,同時還可以傳入自定義的狀態重置邏輯。雖然父元件擁有了改變 toggle 元件內部狀態的途徑,但是如果進一步思考的話,父元件並沒有絕對的控制權。在一些業務場景,我們期望父元件對於子元件的狀態,擁有絕對的控制權。

熟悉 React 的讀者一定不會對智慧元件(Smart Component)和木偶元件(Dump Component)感到陌生。對於後者,其父元件一定對其擁有絕對控制權,因為它內部沒有狀態,渲染邏輯完全取決於父元件所傳 props 的值。而對於前者則相反,由於元件內部會有自己的狀態,它內部的渲染邏輯由父元件所傳 props 與其內部狀態共同決定。

這篇文章將著重解決這個問題,如果能夠使一個智慧元件的狀態變得可控,即:

  • toggle 元件的開關狀態應該完全由 prop 屬性 on 的值決定
  • 當沒有 on 屬性時,toggle 元件的開關狀態降級為內部管理

額外地,我們還將實現一個小需求,toggle 元件的開關狀態至多切換四次,如果超過四次,則需點選重置後,才能夠重新對開關切換狀態進行切換。

實現

判定元件是否受控

由於 toggle 元件為一個智慧元件,我們需要提供一個判定它是否受控的方式。很簡單,由目標中的第一點可知,當父元件傳入了 on 屬性後,toggle 處於被控制的狀態,否則則沒有,於是可以利用 Vue 元件的 computed 特性,宣告一個 isOnControlled 計算屬性,如下:

computed: {
  isOnControlled() {
    return this.on !== undefined;
  }
}
複製程式碼

其內部邏輯很簡單,就是判定 prop 屬性 on 的值是否為 undefined,如果是,則未被父元件控制,反之,則被父元件控制。

更改 on 的宣告方式

由於要滿足目標中提及的第二點,關於 prop 屬性 on 的宣告,我們要做出一些調整,如下:

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

就是簡單地將預設值,由 false 改為了 undefined,這麼做的原因是因為,按照之前的寫法,如果 on 未由父元件傳入,則預設值為 false,那麼 toggle 元件會認為父元件實際傳入了一個值為 falseon 屬性,因此會將其內部的開關狀態控制為,而非降級為內部管理開關狀態。

實現狀態解析邏輯

之前的實現中,通過 scope-slot 注入插槽的狀態完全取決於元件內部 status 的值,我們需要改變狀態的注入邏輯。當元件受控時,其開關狀態應該與 prop 屬性保持一致,反之,則和原來一樣。因此編寫一個叫做 controlledStatus 的計算屬性:

controlledStatus() {
  return this.isOnControlled ? { on: this.on } : this.status;
}
複製程式碼

這裡利用了之前宣告的 isOnControlled 屬性來判斷當前元件是否處於受控狀態。之後相應地把模板中開關狀態的注入邏輯也進行更改:

<slot :status="controlledStatus" :toggle="toggle" :reset="reset"></slot>
複製程式碼

相應地,除了開關狀態的注入邏輯,toggle 方法和 reset 方法的注入邏輯也需要更改,至於為什麼,就交由讀者自行思考得出答案吧,這裡簡單羅列實現程式碼,以供參考:

// toggle 方法
toggle() {
  if (this.isOnControlled) {
    this.$emit("toggle", !this.on);
  } else {
    this.status.on = !this.status.on;
    this.$emit("toggle", this.status.on);
  }
}

// reset 方法
reset() {
  if (this.isOnControlled) {
    Promise.resolve(this.onReset(!this.on)).then(on => {
      this.$emit("reset", on);
    });
  } else {
    Promise.resolve(this.onReset(this.status.on)).then(on => {
      this.status.on = on || false;
      this.$emit("reset", this.status.on);
    });
  }
}
複製程式碼

總體上的思路是,如果元件受控,則傳入回撥方法中的開關狀態引數,是在觸發相應事件後,由 prop 屬性 on 得出的元件在下一時刻,應當處於的狀態。

這麼說可能有點繞,換句話說就是,當元件狀態發生更改時,如果當前的 on 屬性為 true(開關狀態為開),則元件本該處於關的狀態,但由於元件受控,則它內部不能直接將開關狀態更改為關,而是依舊保持為開,但是它會將 false(開關狀態為關)作為引數傳入觸發事件,這將告知父元件,當前元件的下一個狀態為關,至於父元件是否同意將其狀態更改為關則有父元件決定。

如果元件不受控,開關狀態由元件內部自行管理,那和之前的實現邏輯是一模一樣的,保留之前的程式碼即可。

成果

toggle 元件被改造後,實現這個需求就很容易了。關於實現的程式碼,這裡就不進行羅列了,有興趣可以通過線上程式碼連結進行檢視,十分簡單,這裡僅簡單附上一個最終的動態效果圖:

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

總結

關於 Controlled Component 和 Uncontrolled Component 的概念,我第一次是在 React 中關於表單的介紹中接觸到的。實際工作中,大部分對於狀態可控的需求也都存在於表單元件中,之所以存在這樣的需求,是因為表單系統往往是複雜的,將其實現為智慧元件,往往內部狀態過於複雜,而如果實現為木偶元件,程式碼結構或者實現邏輯又過於繁瑣,這時如果可以借鑑這種模式的話,往往可以達到事半功倍的效果。

目錄

github gist

相關文章