作者:Dan Abramov
原文鏈接:
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
容器組件和展示組件
容器組件和展示組件名詞都來自於redux中文文檔。
我在用react寫程序時,發現了一種簡單好用的模式。如果你也熟悉react,或許它早就被你發現了。有一篇文章講得很好,但是,我想補充幾點。
如果你將組件分成兩類,你會發現它們容易更被重用和理解。這兩類我稱之為容器組件和展示組件。我也聽過其他說法,比如"臃腫的"和"苗條的","智能的"和"單調的","多狀態變量"和"單純的","封裝物"和"元件"等等。概念不完全一致,卻有一樣的中心思想。
我所說的展示組件:
- 只關心它們的樣子。
- 可能同時包含子級容器組件和展示組件,一般含DOM標簽和自定的樣式。
- 通常用<code>this.props.children</code>來包含其他組件
- 不依賴app其它組件,比如flux的actions和stores
- 不會定義數據如何讀取,如何改變
- 只通過<code>this.props</code>接受數據和回調函數
- 很少有自己的狀態變量,即使有,也是UI的狀態變量,比如<code>toggleMenuOpen</code>,<code>InputFocus</code>
- 一般是函數級組件,除非它們需要狀態,lifecycle hooks,優化處理。
lifecycle hook這個詞語很形象,但我不知道怎么翻譯得貼切-_-!大概指那些監控組件生命周期里一些關鍵時刻的函數,比如,我需要在這組件初始化的時候調用某函數,那么我實現一個<code>onInit()</code>接口。react中的componentWillMount之類的函數也許也是lifecycle hook?
- 例子有Page,Sidebar,Story,UserInfo,List。
我所說的容器組件
- 只關心它們的運作方式。
- 可能同時包含子級容器組件和展示組件,但大都不含DOM標簽,而含他們自己所用的wrapping div,從不用自己的樣式。
- 為展示組件或其他組件提供數據和方法。
- 調用Flux的actions,並且將其作為展示組件的回調函數。
- 維持許多狀態變量,通常充當一個數據源。
- 通常由高階組件生成,比如Redux里的connect(),Relay里的createContainer(),Flux Utils里的Container.create(),而非手工寫出(譯者:可能在meteor中數據是例外吧)
- 例子有UserPage, FollowersSidebar, StoryContainer, FollowedUserList。
我把他們放在不同的文件夾中,以示區別。
這種方法的好處
- 分離關注,你可以更好的理解app和UI。
- 更易復用,同樣的展示組件可以在不同的狀態源、數據源中使用。也可以封裝成容器組件,在未來重用它們。
- 展示組件是app的調色板。你可以把它們放到單獨的頁面,並讓設計師來調整它們的樣式和結構,而不用改變app的邏輯。單獨的頁面有靜態性,你可以在上面進行screenshot regression測試。
- 這種方法會強迫你去解析布局相關的組件,比如Sidebar, Page, ContextMenu,強迫你去使用this.props.children,而非在不同容器中不斷復制jsx那塊地方。
記住,react的組件不一定要生成DOM,它們只需要考慮如何設計UI之間的分界與組合關系。利用好這一點。
什么時候引入容器?
我的建議是,你最好先做展示組件。當你意識到,有一些中間組件傳遞了過多的props,有一些組件並不使用它們繼承的props而只是將這些props傳遞給他們的子級,而且每次子級組件需要更多數據時,你都需要重新調整或編寫這些中間組件,那么,這時候你可以考慮引入容器組件了。這樣做,你可以傳遞props和方法給末端的子級組件,而不必麻煩一些不相關的中間組件。
這是一個邊寫邊改的重構,所以不必一步到位。你嘗試着這種模式,慢慢地會培養起一種對何時引入容器的直覺,就像你知道何時該增加函數一樣。我的redux教程也許會幫助你哦
其他二分法
容器組件和展示組件的分別並不被嚴格定義,理解這一點很重要。為了對比,我再列舉一些相關(但是不同的)的二分法。
多狀態變量和少狀態變量
有些組件用<code>setState()</code>,有些不用。容器組件通常多狀態變量,而展示組件卻不,這不是鐵規律。展示組件也可以多狀態變量,而容器組件也可以少狀態變量。
類與函數
自從React0.14,組件可以被聲明為類,也可以被聲明為函數。定義函數方便,卻少了類獨有的特性。有些限制或許會在未來消失,但是它們至少是存在的。因為函數容易理解,所以我推薦使用函數,除非你需要那些,現在只有類才有的狀態管理,lifecycle hooks,性能優化等特性。
純的不純的
人們說一個組件純,是指給予它一樣props和state,它保證能輸出一樣的結果。純函數可以是類,可以是函數,可以多狀態,可以無狀態。Another important aspect of pure components is that they don’t rely on deep mutations in props or state, so their rendering performance can be optimized by a shallow comparison in their shouldComponentUpdate() hook。目前只有類可以定義<code>shouldComponentUpdate()</code>,或許將來有改變吧。
展示組件和容器組件都有上述的二分特性。在我看來,展示組件傾向於少狀態、純的函數,容器組件傾向於多狀態,純的類。當然啦,這只是個人觀察,而非規則,我也見過完全相反的情況。
不要將展示組件和容器組件當作教條。有些時候,不必划出清晰的線條,也不用覺得划出區分會很困難。如果你分不清某個組件是展示組件還是容器組件,也許是為時尚早。要知道,心急吃不了熱豆腐。
那讓我們再總結一下不同點:
| 展示組件 | 容器組件 | |
|---|---|---|
| 作用 | 描述如何展現(骨架、樣式) | 描述如何運行(數據獲取、狀態更新) |
| 直接使用 Redux | 否 | 是 |
| 數據來源 | props | 監聽 Redux state |
| 數據修改 | 從 props 調用回調函數 | 向 Redux 派發 actions |
| 調用方式 | 手動 | 通常由 React Redux 生成 |


