受控組件
什么是受控組件?
其值由React控制的輸入表單元素稱為“受控組件”。
受控組件有兩個特點:1. 設置value值,value由state控制,2. value值一般在onChange事件中通過setState進行修改
什么時候使用受控組件?
需要對組件的value值進行修改時,使用受控組件。比如:頁面中有一個按鈕,每點擊一次按鈕受控組件的值加1.
非受控組件
什么是非受控組件?
表單數據由 DOM 處理的組件非受控組件。
非受控組件有兩個特點:1. 不設置value值,2. 通過ref獲取dom節點然后再取value值
<input type="text" placeholder="請輸入姓名" name='username' ref={(input) => this.usernameElem = input}/>
取值方法:this.usernameElem.value
什么時候使用非受控組件?
任何時候都不需要改變組件的value值,這時候可以使用非受控組件。
Vue 中的受控與非受控組件
熟悉 React 的開發者應該對“受控組件”的概念并不陌生,實際上對于任何組件化開發框架而言,都可以實現所謂的受控與非受控,Vue 當然也不例外。并且理解受控與非受控對應的需求場景,可以讓我們在設計一些基礎組件時思路更加清晰,暴露出來的組件 API 也更加合理、統一。
需求
許多 UI 組件都是有狀態(stateful)的,而這個狀態是由組件外部控制還是組件內部維護,也就對應了受控與非受控兩種模式。
例如 Tabs 組件是很常見的一種 UI 組件,它的核心狀態就是記錄當前 active 的 Tab,并且允許用戶切換。
很多時候我們只希望 Tabs 可以正確的展示 active 的內容、并在用戶操作時正常切換,不需要進行任何干預,那么就希望 只需要傳入所有的 Tab 內容,不需要再做額外的配置。
但有的時候我們又希望對 Tabs 的狀態有很強的控制能力,例如多個關聯的 Tabs,子級 Tabs 的內容需要根據父級 Tabs 的 active Tab 動態切換,這時候就會希望 Tabs 組件可以暴露足夠充分的 API,來實現業務的需求。
因此我們可以用一種通用的模式,來讓任意組件的任意狀態同時兼容受控與非受控兩種模式,讓不同需求場景下都可以使用最合理的 API。
簡化示例
我們用一個簡單的 Tabs 實現來演示這種通用的組件 API 設計模式,簡化的部分包括:
可以打開 online DEMO 配合閱讀
API 設計
對于 Vue 組件而言,API 設計主要指的是內部的 data, computed, methods 以及對外的 props, events。在這個示例中,我們會用 activeIdx 作為核心狀態,所有的 API 也都會圍繞這個狀態命名。
非受控模式
如上文所說,非受控模式指的是使用者不需要關心控制組件的狀體,完全交由組件內部維護。
因此我們的 API 會包括:
{
props: {
defaultActiveIdx: {
type: Number,
default: 0
}
},
data() {
return {
localActiveIdx: this.defaultActiveIdx
}
},
methods: {
handleActiveIdxChange(idx) {
this.localActiveIdx = idx;
this.$emit("active-idx-change", idx);
}
}
}
localActiveIdx 是我們用來存放 active index 的組件內 data,對于非受控模式而言,雖然不希望在外部維護狀態,但是仍有可能希望在外部決定初始狀態,所以我們用 defaultActiveIdx 這個 props 決定 localActiveIdx 的初始值。
之后當我們用 v-for="(tab, idx) in tabs" 指令生成所有的 Tab 時,就可以通過 idx === localActiveIdx 的方式判斷當前 Tab 是否 active,再通過 @click="handleActiveIdxChange(idx)" 就可以實現對 localActiveIdx 的更新。
同樣的,我們也可以通過 {{ tabs[localActiveIdx].content }} 展示 active Tab 的內容。
需要注意的是在 handleActiveIdxChange 的事件處理中,我們也 emit 了 active-idx-change 這一事件,這樣可以方便外部在不需要管理組件狀態的同時也可以與組件狀態保持同步。例如我們希望將 active Tab 反映在 URL 中,就可以在外部監聽 active-idx-change 這一事件,并將當前 index 同步到路由中,在將路由中獲取到的 index 作為 defaultActiveIdx 傳入,就可以實現 URL 和 Tabs 的同步。
受控模式
對于受控模式來說,我們可以理解為 active index 是外部傳入的 props,由外部自行維護其狀態。
因此我們只需要添加如下 props:
props: {
activeIdx: Number
}
由于我們已經有對外 emit 的事件 active-idx-change,所以外部用以下方式就可以用一個 data 屬性 externalActiveIdx 維護對應狀態:
<tabs :tabs="tabs" :activeIdx="externalActiveIdx" @active-idx-change="this.externalActiveIdx = $event" />
當然由于在這種模式下外部對狀態有完全的控制權,所以在 active-idx-change 的事件處理中也可以做更為復雜的判斷,例如是否允許激活目標 Tab 之類的校驗。
而在 Tabs 組件內部,我們還需要做一些小的修改。在受控模式中,我們所有狀態相關的處理都是直接使用 localActiveIdx,而現在我們的邏輯應該變為“如果存在 activeIdx props,則使用,否則使用 localActiveIdx”。
為了保證以上邏輯不會讓我們的組件內部實現變得復雜、易錯,我們引入一個 computed 屬性:
computed: {
_activeIdx() {
return this.activeIdx || this.localActiveIdx;
}
}
這樣我們就可以把狀態相關的判斷改為通過 idx === _activeIdx 判斷一個 Tab 是否為激活狀態,也通過 {{ tabs[_activeIdx].content }} 展示 active Tab 的內容。
同樣,我們在 handleActiveIdxChange 的方法內部也可以增加一個判斷,如果存在 props aciveIdx 則不更新 localActiveIdx:
handleActiveIdxChange(idx) {
if (this.activeIdx === undefined) {
this.localActiveIdx = idx;
}
this.$emit("active-idx-change", idx);
}
在一些更復雜的組件中,可能會頻繁判斷是否為受控模式并做不同的處理,這時候通過 this.activeIdx 這樣的核心狀態 props 是否傳入來判斷是否為受控模式是一個不錯的實踐。
總結
最終我們為 active index 設計的完整 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);
}
}
}
通過這種 API 設計方式,可以讓我們設計的基礎組件使用方式更一致,拓展性更強,不論是開發還是使用時思路也會更加簡潔清晰。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。