致敬 React: 爲 Vue 引入容器組件和展現組件

若是你使用過 Redux 開發 React,你必定聽過 容器組件(Smart/Container Components) 或 展現組件(Dumb/Presentational Components),這樣劃分有什麼樣的好處,咱們可否能借鑑這種劃分方式來編寫 Vue 代碼呢?這篇文章會演示爲何咱們應該採起這種模式,以及如何在 Vue 中編寫這兩種組件。前端

爲何要使用容器組件?

假如咱們要寫一個組件來展現評論,在沒聽過容器組件以前,咱們的代碼通常都是這樣寫的:vue

components/CommentList.vue

爲 Vue 引入容器組件和展現組件

store/index.js

爲 Vue 引入容器組件和展現組件

這樣寫看起來理所固然,有沒有什麼問題,或者能夠優化的地方呢?react

有一個很顯而易見的問題,因爲 CommentList.vue 與 項目的 Vuex store 產生了耦合,致使脫離當前的項目很難複用。git

有沒有更好的組件的組織方式,能夠解決這個問題呢?是時候瞭解下 React 社區的容器組件的概念了。github

什麼是容器組件

在 React.js Conf 2015 ,有一個 Making your app fast with high-performance components 的主題介紹了容器組件。vuex

什麼是容器組件

容器組件專門負責和 store 通訊,把數據經過 props 傳遞給普通的展現組件,展現組件若是想發起數據的更新,也是經過容器組件經過 props 傳遞的回調函數來告訴 store。redux

因爲展現組件再也不直接和 store 耦合,而是經過 props 接口來定義本身所需的數據和方法,使得展現組件的可複用性會更高。設計模式

容器組件 和 展現組件 的區別

展現組件 容器組件
做用 描述如何展示(骨架、樣式) 描述如何運行(數據獲取、狀態更新)
直接使用 store
數據來源 props 監聽 store state
數據修改 從 props 調用回調函數 向 store 派發 actions

來自 Redux 文檔 https://user-gold-cdn.xitu.io/2018/5/2/1631f590aa5512b7數組

用 容器組件/展現組件 模式改造上面的例子

針對最初的例子,如何快速按照這種模式來劃分組件呢?咱們主要針對 CommentList.vue 進行拆分,首先是基本的概要設計:promise

概要設計

展現組件

  • components/CommentListNew.vue 這是一個新的評論展現組件,用於展現評論
    • comments: Array prop 接收以 { id, author, body } 形式顯示的 comment 項數組。
    • fetch() 接收更新評論數據的方法

展現組件只定義外觀並不關心數據來源和如何改變。傳入什麼就渲染什麼。

comments、fetch 等這些 props 並不關心背後是不是由 Vuex 提供的,你可使用 Vuex,或者其餘狀態管理庫,甚至是一個 EventBus,均可以複用這些展現組件。

同時,能夠利用 props 的類型和驗證來約束傳入的內容,好比驗證傳入的 comments 是不是一個含有指定字段的對象,這在以前混合組件的狀況是下是沒有的,提升了代碼的健壯性。

容器組件

  • containers/CommentListContainer.vue 將 CommentListNew 組件鏈接到 store

容器組件能夠將 store 對應的 state 或者 action 等封裝傳入展現組件。

編碼實現

Talk is cheap, show me the code!

components/CommentListNew.vue

這個文件再也不依賴 store,改成從 props 傳遞。

值得注意的是 comments 和 fetch 分別定義了 type 、default 和 validator,用以定義和驗證 props。

爲 Vue 引入容器組件和展現組件

containers/CommentListContainer.vue

容器組件的職責

  • 經過 computed 來獲取到狀態更新,傳遞給展現組件
  • 經過 methods 定義回調函數,回調函數內部調用 store 的 dispatch 方法,傳遞給展現組件

爲 Vue 引入容器組件和展現組件

使用 @xunlei/vuex-connector 實現容器組件

上面演示的容器組件的代碼很是簡單,實際上若是直接投入生產環境,會產生一些問題。

手動實現容器組件存在的不足

代碼比較繁瑣

在上面的例子中,每次傳遞一個 state 都要定義一個 computed,每傳遞一個 mutation 或者 action 都須要定義一個方法,並且還要注意這個方法的參數要透傳過去,同時還要處理返回值,好比異步的 action 須要返回 promise 的時候,定義的這個 method 也得把 action 的返回值返回出去。

沒法透傳其餘 props 給展現組件

好比展現組件新增了一個 prop 叫作 type,能夠傳遞一個評論的類型,用來區分是熱門仍是最新,若是用上面的容器實現方式,首先須要在容器組件這層新增一個 prop 叫作 type 接受外部傳來的參數,而後在展現組件內部一樣定義一個 叫作 type 的 prop,而後才能傳遞下去。

須要透傳的 props 必須定義兩遍,增長了維護的成本。

爲 Vue 引入容器組件和展現組件

爲 Vue 引入容器組件和展現組件

容器組件沒法統一進行優化

每個手動實現的容器組件實質上代碼邏輯很是近似,可是沒有通過同一層封裝,若是目前實現的容器組件存在一些性能優化的地方,須要每一個容器組件都進行統一的修改。

沒法控制展現組件不去獲取 store

由於容器組件是經過 this.$store 獲取 store 的,展現組件內部實質上也能夠直接跟 store 通訊,若是沒有約束,很難統一要求展現組件不得直接和 store 通訊。

使用 @xunlei/vuex-connector

@xunlei/vuex-connector 借鑑了 react redux 的 connect 方法,在 vuex 基礎上進行的開發。

有如下幾個特色:

代碼很是簡潔

下面是上面例子中手動實現的容器組件的改造版本:

comonents/ConnectCommentListContainer.vue

爲 Vue 引入容器組件和展現組件

經過 connector 的 connnect 方法,傳入要映射的配置,支持 mapStateToProps, mapGettersToProps, mapDispatchToProps, mapCommitToProps 這四種,每一種都是隻要配置一個簡單的 map 函數,或者字符串便可。

而後在返回的函數中傳入要鏈接的展現組件便可,是否是很是的簡潔。

容器組件自己也能夠複用

因爲借鑑了 redux 優雅的函數式風格, connector 的 connnect 方法 返回的函數其實是一個高階組件,也就是一個能夠建立組件的函數。這樣帶來了額外的好處,不一樣的展現組件也能夠複用同一個容器組件。

舉個例子:

若是你寫了多個版本的評論展現組件,接受的數據和更新數據的方式都是同樣的,那麼你就沒有必要爲每一個版本的評論組件都搞一個容器組件了,只要複用同一個高階組件函數便可。

問題來了,connector 是什麼?

connector 其實是一個能獲取到 store 實例的鏈接器,能夠在初始化 vuex store 的時候進行初始化。

爲 Vue 引入容器組件和展現組件

一個 Vue 程序實際上只須要初始化一次便可。

支持透傳其餘 props 給展現組件

VuexConnector 實現的時候採用了函數式組件( functional: true )

函數式組件是無狀態 (沒有響應式數據),無實例 (沒有 this 上下文)。

在做爲包裝組件時函數式組件很是有用,好比,當你須要作這些時:

  • 程序化地在多個組件中選擇一個
  • 在將 children, props, data 傳遞給子組件以前操做它們。

另外,函數式組件只是一個函數,因此渲染開銷也低不少。然而,對持久化實例的缺少也意味着函數式組件不會出如今 Vue devtools 的組件樹裏。

所以須要透傳的 props 能夠直接透傳,須要經過 map 方式從 store 裏進行獲取的 props 直接會根據配置生成。

統一封裝方便後續統一優化

VuexConnector.connect 方法將原本須要重複作的事情進行了抽象,也帶來了後期進行統一優化和升級的便利。

能夠控制展現組件沒法直接與 store 通訊

VuexConnector 不依賴 this.$store,而是依賴初始化傳入的 store 實例,容器組件能夠用 connect 將展現組件與 store 進行鏈接。

因爲不依賴 this.$store,咱們在程序入口 new Vue 的時候,就不須要傳入 store 實例了。

好比,以前咱們是經過下面的方式進行初始化:

爲 Vue 引入容器組件和展現組件

使用了 VuexConnector 以後,在最初 new Vue 的時候就不須要也最好不要傳遞 store 了,這樣就避免了 this.$store 氾濫致使代碼耦合的問題。

引入容器組件/展現組件模式帶來的好處

可複用性

容器組件/展現組件的劃分,採用了單一職責原則的設計模式,容器組件專門負責和 store 通訊,展現組件只負責展現,解除了組件的耦合,能夠帶來更好的可複用性。

健壯性

因爲展現組件和容器組件是經過 props 這種接口來鏈接,能夠利用 props 的校驗來加強代碼的可靠性,混合的組件就沒有這種好處。

另外對 props 的校驗能夠採起一下幾種方式:

Vue 組件 props 驗證

能夠驗證 props 的類型,默承認以校驗是不是如下類型:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol

若是你的 props 是類的一個實例,type 也能夠是一個自定義構造器函數,使用 instanceof 檢測。

若是仍是不知足需求,能夠自定義驗證函數:

爲 Vue 引入容器組件和展現組件

TypeScript 類型系統

Vue 組件 props 驗證對於對象或者其餘複雜的類型校驗仍是不太友好,因此不少人也推薦你們的 props 儘可能採起簡單類型,不過若是你有在用 TypeScript 開發 Vue 應用,能夠利用 TypeScript 靜態類型檢查來聲明你的 props 。

爲 Vue 引入容器組件和展現組件

可測試性

因爲組件作的事情更少了,使得測試也會變得容易。

容器組件不用關心 UI 的展現,只關心數據和更新。

展現組件只是呈現傳入的 props ,寫單元測試的時候也很是容易 mock 數據層。

引入容器組件/展現組件模式帶來的限制

學習和開發成本

由於容器組件/展現組件的拆分,初期會增長一些學習成本,不過當你看完這篇文章,基本上也就入門了。

在開發的時候,因爲須要封裝一個容器,包裝一些數據和接口給展現組件,會增長一些工做量, @xunlei/vuex-connector 經過配置的方式能夠減輕很多你的工做量。

另外,在展現組件內對 props 的聲明也會帶來少許的工做。

整體來講,引入容器組件/展現組件模式投入產出比仍是比較值得的。

延伸閱讀

代碼示例

Vue Vuex 容器-展現組件模式 Demo

Demo 在線地址:

xunleif2e.github.io/vue-vuex-co…

Demo 源碼:

github.com/xunleif2e/v…

@xunlei/vuex-connector

基於 Vue 生態實現的Vuex store Connector

@xunlei/vuex-connector 源碼:

github.com/xunleif2e/v…

歡迎 Star

掃一掃關注迅雷前端公衆號

做者:binggg

校對:珈藍

相關文章
相關標籤/搜索