溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Vue 中的受控與非受控組件的實(shí)現(xiàn)

發(fā)布時(shí)間:2020-09-29 15:31:28 來(lái)源:腳本之家 閱讀:215 作者:aryu 欄目:web開(kāi)發(fā)

受控組件

什么是受控組件?

其值由React控制的輸入表單元素稱(chēng)為“受控組件”。

受控組件有兩個(gè)特點(diǎn):1. 設(shè)置value值,value由state控制,2. value值一般在onChange事件中通過(guò)setState進(jìn)行修改

什么時(shí)候使用受控組件?

需要對(duì)組件的value值進(jìn)行修改時(shí),使用受控組件。比如:頁(yè)面中有一個(gè)按鈕,每點(diǎn)擊一次按鈕受控組件的值加1.

非受控組件

什么是非受控組件?

表單數(shù)據(jù)由 DOM 處理的組件非受控組件。

非受控組件有兩個(gè)特點(diǎn):1. 不設(shè)置value值,2. 通過(guò)ref獲取dom節(jié)點(diǎn)然后再取value值

<input type="text" placeholder="請(qǐng)輸入姓名" name='username' ref={(input) => this.usernameElem = input}/>

取值方法:this.usernameElem.value

什么時(shí)候使用非受控組件?

任何時(shí)候都不需要改變組件的value值,這時(shí)候可以使用非受控組件。

Vue 中的受控與非受控組件

熟悉 React 的開(kāi)發(fā)者應(yīng)該對(duì)“受控組件”的概念并不陌生,實(shí)際上對(duì)于任何組件化開(kāi)發(fā)框架而言,都可以實(shí)現(xiàn)所謂的受控與非受控,Vue 當(dāng)然也不例外。并且理解受控與非受控對(duì)應(yīng)的需求場(chǎng)景,可以讓我們?cè)谠O(shè)計(jì)一些基礎(chǔ)組件時(shí)思路更加清晰,暴露出來(lái)的組件 API 也更加合理、統(tǒng)一。

需求

許多 UI 組件都是有狀態(tài)(stateful)的,而這個(gè)狀態(tài)是由組件外部控制還是組件內(nèi)部維護(hù),也就對(duì)應(yīng)了受控與非受控兩種模式。

例如 Tabs 組件是很常見(jiàn)的一種 UI 組件,它的核心狀態(tài)就是記錄當(dāng)前 active 的 Tab,并且允許用戶切換。

很多時(shí)候我們只希望 Tabs 可以正確的展示 active 的內(nèi)容、并在用戶操作時(shí)正常切換,不需要進(jìn)行任何干預(yù),那么就希望 只需要傳入所有的 Tab 內(nèi)容,不需要再做額外的配置。

但有的時(shí)候我們又希望對(duì) Tabs 的狀態(tài)有很強(qiáng)的控制能力,例如多個(gè)關(guān)聯(lián)的 Tabs,子級(jí) Tabs 的內(nèi)容需要根據(jù)父級(jí) Tabs 的 active Tab 動(dòng)態(tài)切換,這時(shí)候就會(huì)希望 Tabs 組件可以暴露足夠充分的 API,來(lái)實(shí)現(xiàn)業(yè)務(wù)的需求。

因此我們可以用一種通用的模式,來(lái)讓任意組件的任意狀態(tài)同時(shí)兼容受控與非受控兩種模式,讓不同需求場(chǎng)景下都可以使用最合理的 API。

簡(jiǎn)化示例

我們用一個(gè)簡(jiǎn)單的 Tabs 實(shí)現(xiàn)來(lái)演示這種通用的組件 API 設(shè)計(jì)模式,簡(jiǎn)化的部分包括:

  • 用 index 來(lái)作為 Tab 的唯一標(biāo)識(shí)
  • Tab content 只支持字符串

可以打開(kāi) online DEMO 配合閱讀

API 設(shè)計(jì)

對(duì)于 Vue 組件而言,API 設(shè)計(jì)主要指的是內(nèi)部的 data, computed, methods 以及對(duì)外的 props, events。在這個(gè)示例中,我們會(huì)用 activeIdx 作為核心狀態(tài),所有的 API 也都會(huì)圍繞這個(gè)狀態(tài)命名。

非受控模式

如上文所說(shuō),非受控模式指的是使用者不需要關(guān)心控制組件的狀體,完全交由組件內(nèi)部維護(hù)。

因此我們的 API 會(huì)包括:

{
 props: {
  defaultActiveIdx: {
   type: Number,
   default: 0
  }
 },
 data() {
  return {
   localActiveIdx: this.defaultActiveIdx
  }
 },
 methods: {
  handleActiveIdxChange(idx) {
   this.localActiveIdx = idx;
   this.$emit("active-idx-change", idx);
  }
 }
}

localActiveIdx 是我們用來(lái)存放 active index 的組件內(nèi) data,對(duì)于非受控模式而言,雖然不希望在外部維護(hù)狀態(tài),但是仍有可能希望在外部決定初始狀態(tài),所以我們用 defaultActiveIdx 這個(gè) props 決定 localActiveIdx 的初始值。

之后當(dāng)我們用 v-for="(tab, idx) in tabs" 指令生成所有的 Tab 時(shí),就可以通過(guò) idx === localActiveIdx 的方式判斷當(dāng)前 Tab 是否 active,再通過(guò) @click="handleActiveIdxChange(idx)" 就可以實(shí)現(xiàn)對(duì) localActiveIdx 的更新。

同樣的,我們也可以通過(guò) {{ tabs[localActiveIdx].content }} 展示 active Tab 的內(nèi)容。

需要注意的是在 handleActiveIdxChange 的事件處理中,我們也 emit 了 active-idx-change 這一事件,這樣可以方便外部在不需要管理組件狀態(tài)的同時(shí)也可以與組件狀態(tài)保持同步。例如我們希望將 active Tab 反映在 URL 中,就可以在外部監(jiān)聽(tīng) active-idx-change 這一事件,并將當(dāng)前 index 同步到路由中,在將路由中獲取到的 index 作為 defaultActiveIdx 傳入,就可以實(shí)現(xiàn) URL 和 Tabs 的同步。

受控模式

對(duì)于受控模式來(lái)說(shuō),我們可以理解為 active index 是外部傳入的 props,由外部自行維護(hù)其狀態(tài)。

因此我們只需要添加如下 props:

props: {
 activeIdx: Number
}

由于我們已經(jīng)有對(duì)外 emit 的事件 active-idx-change,所以外部用以下方式就可以用一個(gè) data 屬性 externalActiveIdx 維護(hù)對(duì)應(yīng)狀態(tài):

<tabs
 :tabs="tabs"
 :activeIdx="externalActiveIdx"
 @active-idx-change="this.externalActiveIdx = $event"
/>

當(dāng)然由于在這種模式下外部對(duì)狀態(tài)有完全的控制權(quán),所以在 active-idx-change 的事件處理中也可以做更為復(fù)雜的判斷,例如是否允許激活目標(biāo) Tab 之類(lèi)的校驗(yàn)。

而在 Tabs 組件內(nèi)部,我們還需要做一些小的修改。在受控模式中,我們所有狀態(tài)相關(guān)的處理都是直接使用 localActiveIdx,而現(xiàn)在我們的邏輯應(yīng)該變?yōu)椤叭绻嬖?activeIdx props,則使用,否則使用 localActiveIdx”。

為了保證以上邏輯不會(huì)讓我們的組件內(nèi)部實(shí)現(xiàn)變得復(fù)雜、易錯(cuò),我們引入一個(gè) computed 屬性:

computed: {
 _activeIdx() {
  return this.activeIdx || this.localActiveIdx;
 }
}

這樣我們就可以把狀態(tài)相關(guān)的判斷改為通過(guò) idx === _activeIdx 判斷一個(gè) Tab 是否為激活狀態(tài),也通過(guò) {{ tabs[_activeIdx].content }} 展示 active Tab 的內(nèi)容。

同樣,我們?cè)?handleActiveIdxChange 的方法內(nèi)部也可以增加一個(gè)判斷,如果存在 props aciveIdx 則不更新 localActiveIdx:

handleActiveIdxChange(idx) {
 if (this.activeIdx === undefined) {
  this.localActiveIdx = idx;
 }
 this.$emit("active-idx-change", idx);
}

在一些更復(fù)雜的組件中,可能會(huì)頻繁判斷是否為受控模式并做不同的處理,這時(shí)候通過(guò) this.activeIdx 這樣的核心狀態(tài) props 是否傳入來(lái)判斷是否為受控模式是一個(gè)不錯(cuò)的實(shí)踐。

總結(jié)

最終我們?yōu)?active index 設(shè)計(jì)的完整 API 如下:

{
 props: {
  activeIdx: Number,
  defaultActiveIdx: {
   type: Number,
   default: 0
  }
 },
 data() {
  return {
   localActiveIdx: this.defaultActiveIdx
  };
 },
 computed: {
  _activeIdx() {
   return this.activeIdx || this.localActiveIdx;
  }
 },
 methods: {
  handleActiveIdxChange(idx) {
   if (this.activeIdx === undefined) {
    this.localActiveIdx = idx;
   }
   this.$emit("active-idx-change", idx);
  }
 }
}

通過(guò)這種 API 設(shè)計(jì)方式,可以讓我們?cè)O(shè)計(jì)的基礎(chǔ)組件使用方式更一致,拓展性更強(qiáng),不論是開(kāi)發(fā)還是使用時(shí)思路也會(huì)更加簡(jiǎn)潔清晰。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI