您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)怎么在React中為 Vue 引入容器組件,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
Vue具體輕量級框架、簡單易學(xué)、雙向數(shù)據(jù)綁定、組件化、數(shù)據(jù)和結(jié)構(gòu)的分離、虛擬DOM、運行速度快等優(yōu)勢,Vue中頁面使用的是局部刷新,不用每次跳轉(zhuǎn)頁面都要請求所有數(shù)據(jù)和dom,可以大大提升訪問速度和用戶體驗。
假如我們要寫一個組件來展示評論,在沒聽過容器組件之前,我們的代碼一般都是這樣寫的:
components/CommentList.vue
<template> <ul> <li v-for="comment in comments" :key="comment.id" > {{comment.body}}—{{comment.author}} </li> </ul> </template> <script> export default { name: 'CommentList', computed: { comments () { return this.$store.state.comments } }, mounted () { this.$store.dispatch('fetchComments') } } </script>
store/index.js
import Vue from 'vue'; import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({ state: { comments: [], }, mutations: { setComments(state, comments) { state.comments = comments; }, }, actions: { fetchComments({commit}) { setTimeout(() => { commit('setComments', [ { body: '霸氣側(cè)漏', author: '雷叔', id: 1123, }, { body: '機智如我', author: '蕾妹', id: 1124, }, ]); }); }, }, });
export default store;
這樣寫看起來理所當(dāng)然,有沒有什么問題,或者可以優(yōu)化的地方呢?
有一個很顯而易見的問題,由于 CommentList.vue 與 項目的 Vuex store 產(chǎn)生了耦合,導(dǎo)致脫離當(dāng)前的項目很難復(fù)用。
有沒有更好的組件的組織方式,可以解決這個問題呢?是時候了解下 React 社區(qū)的容器組件的概念了。
什么是容器組件
在 React.js Conf 2015 ,有一個 Making your app fast with high-performance components 的主題介紹了容器組件。
容器組件專門負(fù)責(zé)和 store 通信,把數(shù)據(jù)通過 props 傳遞給普通的展示組件,展示組件如果想發(fā)起數(shù)據(jù)的更新,也是通過容器組件通過 props 傳遞的回調(diào)函數(shù)來告訴 store。
由于展示組件不再直接和 store 耦合,而是通過 props 接口來定義自己所需的數(shù)據(jù)和方法,使得展示組件的可復(fù)用性會更高。
容器組件 和 展示組件 的區(qū)別
展示組件 | 容器組件 | |
---|---|---|
作用 | 描述如何展現(xiàn)(骨架、樣式) | 描述如何運行(數(shù)據(jù)獲取、狀態(tài)更新) |
直接使用 store | 否 | 是 |
數(shù)據(jù)來源 | props | 監(jiān)聽 store state |
數(shù)據(jù)修改 | 從 props 調(diào)用回調(diào)函數(shù) | 向 store 派發(fā) actions |
來自 Redux 文檔 https://user-gold-cdn.xitu.io/2018/5/2/1631f590aa5512b7
用 容器組件/展示組件 模式改造上面的例子
針對最初的例子,如何快速按照這種模式來劃分組件呢?我們主要針對 CommentList.vue 進(jìn)行拆分,首先是基本的概要設(shè)計:
概要設(shè)計
展示組件
components/CommentListNew.vue 這是一個新的評論展示組件,用于展示評論
comments: Array prop 接收以 { id, author, body } 形式顯示的 comment 項數(shù)組。
fetch() 接收更新評論數(shù)據(jù)的方法
展示組件只定義外觀并不關(guān)心數(shù)據(jù)來源和如何改變。傳入什么就渲染什么。
comments、fetch 等這些 props 并不關(guān)心背后是否是由 Vuex 提供的,你可以使用 Vuex,或者其他狀態(tài)管理庫,甚至是一個 EventBus,都可以復(fù)用這些展示組件。
同時,可以利用 props 的類型和驗證來約束傳入的內(nèi)容,比如驗證傳入的 comments 是否是一個含有指定字段的對象,這在之前混合組件的情況是下是沒有的,提高了代碼的健壯性。
容器組件
containers/CommentListContainer.vue 將 CommentListNew 組件連接到 store
容器組件可以將 store 對應(yīng)的 state 或者 action 等封裝傳入展示組件。
編碼實現(xiàn)
Talk is cheap, show me the code!
components/CommentListNew.vue
這個文件不再依賴 store,改為從 props 傳遞。
值得注意到是 comments 和 fetch 分別定義了 type 、default 和 validator,用以定義和驗證 props。
<template> <ul> <li v-for="comment in comments" :key="comment.id" > {{comment.body}}—{{comment.author}} </li> </ul> </template> <script> export default { name: 'CommentListNew', props: { comments: { type: Array, default () { return [] }, validator (comments) { return comments.every(comment => 'body' in comment && 'author' in comment && 'id' in comment ) } }, fetch: { type: Function, default: () => {} } }, mounted () { this.fetch() } } </script>
containers/CommentListContainer.vue
容器組件的職責(zé)
通過 computed 來獲取到狀態(tài)更新,傳遞給展示組件
通過 methods 定義回調(diào)函數(shù),回調(diào)函數(shù)內(nèi)部調(diào)用 store 的 dispatch 方法,傳遞給展示組件
<template> <CommentList :comments="comments" :fetch="fetchComments" ></CommentList> </template> <script> import CommentList from '@/components/CommentListNew' export default { name: 'CommentListContainer', components: { CommentList }, computed: { comments () { return this.$store.state.comments } }, methods: { fetchComments () { return this.$store.dispatch('fetchComments') } } } </script>
使用 @xunlei/vuex-connector 實現(xiàn)容器組件
上面演示的容器組件的代碼非常簡單,實際上如果直接投入生產(chǎn)環(huán)境,會產(chǎn)生一些問題。
手動實現(xiàn)容器組件存在的不足
代碼比較繁瑣
在上面的例子中,每次傳遞一個 state 都要定義一個 computed,每傳遞一個 mutation 或者 action 都需要定一個方法,而且還要注意這個方法的參數(shù)要透傳過去,同時還要處理返回值,比如異步的 action 需要返回 promise 的時候,定義的這個 method 也得把 action 的返回值返回出去。
無法透傳其他 props 給展示組件
比如展示組件新增了一個 prop 叫做 type,可以傳遞一個評論的類型,用來區(qū)分是熱門還是最新,如果用上面的容器實現(xiàn)方式,首先需要在容器組件這層新增一個 prop 叫做 type 接受外部傳來的參數(shù),然后在展示組件內(nèi)部同樣定義一個 叫做 type 的 prop,然后才能傳遞下去。
需要透傳的 prop 必須定義兩遍,增加了維護(hù)的成本。
<CommentListContainer type="熱門"></CommentListContainer> <CommentList :fetch="fetchComments" :comments="comments" :type="type" ></CommentList>
容器組件無法統(tǒng)一進(jìn)行優(yōu)化
每一個手動實現(xiàn)的容器組件實質(zhì)上代碼邏輯非常近似,但是沒有經(jīng)過同一層封裝,如果目前實現(xiàn)的容器組件存在一些性能優(yōu)化的地方,需要每個容器組件都進(jìn)行統(tǒng)一的修改。
無法控制展示組件不去獲取 store
因為容器組件是通過 this.$store 獲取 store 的,展示組件內(nèi)部實質(zhì)上也可以直接跟 store 通信,如果沒有約束,很難統(tǒng)一要求展示組件不得直接和 store 通信。
使用 @xunlei/vuex-connector
@xunlei/vuex-connector 借鑒了 react redux 的 connect 方法,在 vuex 基礎(chǔ)上進(jìn)行的開發(fā)。
有以下幾個特點:
代碼非常簡潔
下面是上面例子中手動實現(xiàn)的容器組件的改造版本:
comonents/ConnectCommentListContainer.vue
<script> import CommentListNew from '@/components/CommentListNew' import { connector } from '@/store' export default connector.connect({ mapStateToProps: { comments: (state) => state.comments }, mapActionToProps: { fetch: 'fetchComments' } })(CommentListNew) </script>
通過 connector 的 connnect 方法,傳入要映射的配置,支持 mapStateToProps, mapGettersToProps, mapDispatchToProps, mapCommitToProps 這四種,每一種都是只要配置一個簡單的 map 函數(shù),或者字符串即可。
然后在返回的函數(shù)中傳入要連接的展示組件,是不是非常的簡潔,同時借鑒了 redux 優(yōu)雅的函數(shù)式風(fēng)格。
問題來了,connector 是什么?
connector 實際上是一個能獲取到 store 實例的連接器,可以在初始化 vuex store 的時候進(jìn)行初始化。
import Vue from 'vue'; import Vuex from 'vuex'; import VuexConnector from '@xunlei/vuex-connector'; Vue.use(Vuex); const store = new Vuex.Store({ // your store }); export const connector = new VuexConnector(store); export default store;
一個 Vue 程序?qū)嶋H上只需要初始化一次即可。
支持透傳其他 props 給展示組件
VuexConnector 實現(xiàn)的時候采用了函數(shù)式組件( functional: true )
函數(shù)式組件是無狀態(tài) (沒有響應(yīng)式數(shù)據(jù)),無實例 (沒有 this 上下文)。
在作為包裝組件時函數(shù)式組件非常有用,比如,當(dāng)你需要做這些時:
程序化地在多個組件中選擇一個
在將 children, props, data 傳遞給子組件之前操作它們。
另外,函數(shù)式組件只是一個函數(shù),所以渲染開銷也低很多。然而,對持久化實例的缺乏也意味著函數(shù)式組件不會出現(xiàn)在 Vue devtools 的組件樹里。
因此需要透傳的 props 可以直接透傳,需要通過 map 方式從 store 里進(jìn)行獲取的 props 直接會根據(jù)配置生成。
統(tǒng)一封裝方便后續(xù)統(tǒng)一優(yōu)化
VuexConnector.connect 方法將本來需要重復(fù)做的事情進(jìn)行了抽象,也帶來了后期進(jìn)行統(tǒng)一優(yōu)化和升級的便利。
可以控制展示組件無法直接與 store 通信
VuexConnector 不依賴 this.$store,而是依賴初始化傳入的 store 實例,容器組件可以用 connect 將展示組件與 store 進(jìn)行連接。
由于不依賴 this.$store,我們在程序入口 new Vue 的時候,就不需要傳入 store 實例了。
比如,之前我們是通過下面的方式進(jìn)行初始化:
import Vue from 'vue'; import App from './App'; import store from './store'; new Vue({ el: '#app', components: {App}, template: '<App/>', store, });
使用了 VuexConnector 之后,在最初 new Vue 的時候就不需要也最好不要傳遞 store 了,這樣就避免了 this.$store 泛濫導(dǎo)致代碼耦合的問題。
引入容器組件/展示組件模式帶來的好處
可復(fù)用性
容器組件/展示組件的劃分,采用了單一職責(zé)原則的設(shè)計模式,容器組件專門負(fù)責(zé)和 store 通信,展示組件只負(fù)責(zé)展示,解除了組件的耦合,可以帶來更好的可復(fù)用性。
健壯性
由于展示組件和容器組件是通過 prop 這種接口來連接,可以利用 props 的校驗來增強代碼的可靠性,混合的組件就沒有這種好處。
另外對 props 的校驗可以采取一下幾種方式:
Vue 組件 props 驗證
可以驗證 props 的類型,默認(rèn)可以校驗是否是以下類型:
String
Number
Boolean
Function
Object
Array
Symbol
如果你的 props 是類的一個實例,type 也可以是一個自定義構(gòu)造器函數(shù),使用 instanceof 檢測。
如果還是不滿足需求,可以自定義驗證函數(shù):
// 自定義驗證函數(shù) propF: { validator: function (value) { return value > 10 } }
TypeScript 類型系統(tǒng)
Vue 組件 props 驗證對于對象或者其他復(fù)雜的類型校驗還是不太友好,所以很多人也推薦大家的 props 盡量采取簡單類型,不過如果你有在用 TypeScript 開發(fā) Vue 應(yīng)用,可以利用 TypeScript 靜態(tài)類型檢查來聲明你的 props 。
@Component export default class Hello extends Vue { @Prop info: IHelloInfo; // 這里可以用你自定義的 interface }
可測試性
由于組件做的事情更少了,使得測試也會變得容易。
容器組件不用關(guān)心 UI 的展示,只關(guān)心數(shù)據(jù)和更新。
展示組件只是呈現(xiàn)傳入的 props ,寫單元測試的時候也非常容易 mock 數(shù)據(jù)層。
引入容器組件/展示組件模式帶來的限制
學(xué)習(xí)和開發(fā)成本
因為容器組件/展示組件的拆分,初期會增加一些學(xué)習(xí)成本,不過當(dāng)你看完這篇文章,基本上也就入門了。
在開發(fā)的時候,由于需要封裝一個容器,包裝一些數(shù)據(jù)和接口給展示組件,會增加一些工作量, @xunlei/vuex-connector 通過配置的方式可以減輕不少你的工作量。
另外,在展示組件內(nèi)對 props 的聲明也會帶來少量的工作。
總體來說,引入容器組件/展示組件模式投入產(chǎn)出比還是比較值得的。
看完上述內(nèi)容,你們對怎么在React中為 Vue 引入容器組件有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。