你們已經知道,只會接受 props
而且渲染肯定結果的組件咱們把它叫作 Dumb 組件,這種組件只關心一件事情 —— 根據 props
進行渲染。html
Dumb 組件最好不要依賴除了 React.js 和 Dumb 組件之外的內容。它們不要依賴 Redux 不要依賴 React-redux。這樣的組件的可複用性是最好的,其餘人能夠安心地使用而不用怕會引入什麼奇奇怪怪的東西。react
當咱們拿到一個需求開始劃分組件的時候,要認真考慮每一個被劃分紅組件的單元到底會不會被複用。若是這個組件可能會在多處被使用到,那麼咱們就把它作成 Dumb 組件。redux
咱們可能拆分了一堆 Dumb 組件出來。可是單純靠 Dumb 是沒有辦法構建應用程序的,由於它們實在太「笨」了,對數據的力量一無所知。因此還有一種組件,它們很是聰明(smart),城府很深精通算計,咱們叫它們 Smart 組件。它們專門作數據相關的應用邏輯,和各類數據打交道、和 Ajax 打交道,而後把數據經過 props
傳遞給 Dumb,它們帶領着 Dumb 組件完成了複雜的應用程序邏輯。函數
Smart 組件不用考慮太多複用性問題,它們就是用來執行特定應用邏輯的。Smart 組件可能組合了 Smart 組件和 Dumb 組件;可是 Dumb 組件儘可能不要依賴 Smart 組件。由於 Dumb 組件目的之一是爲了複用,一旦它引用了 Smart 組件就至關於帶入了一堆應用邏輯,致使它沒法無用,因此儘可能不要幹這種事情。一旦一個可複用的 Dumb 組件之下引用了一個 Smart 組件,就至關於污染了這個 Dumb 組件樹。若是一個組件是 Dumb 的,那麼它的子組件們都應該是 Dumb 的纔對。this
知道了組件有這兩種分類之後,咱們來從新審視一下以前的 make-react-redux
工程裏面的組件,例如 src/Header.js
:spa
import React, { Component } from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' class Header extends Component { static propTypes = { themeColor: PropTypes.string } render () { return ( <h1 style={{ color: this.props.themeColor }}>React.js 小書</h1> ) } } const mapStateToProps = (state) => { return { themeColor: state.themeColor } } Header = connect(mapStateToProps)(Header) export default Header
這個組件究竟是 Smart 仍是 Dumb 組件?這個文件其實依賴了 react-redux
,別人使用的時候其實會帶上這個依賴,因此這個組件不能叫 Dumb 組件。可是你觀察一下,這個組件在 connect
以前它倒是 Dumb 的,就是由於 connect
了致使它和 context 扯上了關係,致使它變 Smart 了,也使得這個組件沒有了很好的複用性。3d
爲了解決這個問題,咱們把 Smart 和 Dumb 組件分開到兩個不一樣的目錄,再也不在 Dumb 組件內部進行 connect
,在 src/
目錄下新建兩個文件夾 components/
和 containers/
:code
src/ components/ containers/
咱們規定:全部的 Dumb 組件都放在 components/
目錄下,全部的 Smart 的組件都放在 containers/
目錄下,這是一種約定俗成的規則。component
刪除 src/Header.js
,新增 src/components/Header.js
:htm
import React, { Component } from 'react' import PropTypes from 'prop-types' export default class Header extends Component { static propTypes = { themeColor: PropTypes.string } render () { return ( <h1 style={{ color: this.props.themeColor }}>React.js 小書</h1> ) } }
如今 src/components/Header.js
毫無疑問是一個 Dumb 組件,它除了依賴 React.js 什麼都不依賴。咱們新建 src/container/Header.js
,這是一個與之對應的 Smart 組件:
import { connect } from 'react-redux' import Header from '../components/Header' const mapStateToProps = (state) => { return { themeColor: state.themeColor } } export default connect(mapStateToProps)(Header)
它會從導入 Dumb 的 Header.js
組件,進行 connect
一番變成 Smart 組件,而後把它導出模塊。
這樣咱們就把 Dumb 組件抽離出來了,如今 src/components/Header.js
可複用性很是強,別的同事能夠隨意用它。而 src/containers/Header.js
則是跟業務相關的,咱們只用在特定的應用場景下。咱們能夠繼續用這種方式來重構其餘組件。
接下來的狀況就有點意思了,能夠趁機給你們講解一下組件劃分的一些原則。咱們看看這個應用原來的組件樹:
對於 Content
這個組件,能夠看到它是依賴 ThemeSwitch
組件的,這就須要好好思考一下了。咱們分兩種狀況來討論:Content
不復用和可複用。
若是產品場景並無要求說 Content
須要複用,它只是在特定業務須要而已。那麼沒有必要把 Content
作成 Dumb 組件了,就讓它成爲一個 Smart 組件。由於 Smart 組件是可使用 Smart 組件的,因此 Content
可使用 Dumb 的 ThemeSwitch
組件 connect
的結果。
新建一個 src/components/ThemeSwitch.js
:
import React, { Component } from 'react' import PropTypes from 'prop-types' export default class ThemeSwitch extends Component { static propTypes = { themeColor: PropTypes.string, onSwitchColor: PropTypes.func } handleSwitchColor (color) { if (this.props.onSwitchColor) { this.props.onSwitchColor(color) } } render () { return ( <div> <button style={{ color: this.props.themeColor }} onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button> <button style={{ color: this.props.themeColor }} onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button> </div> ) } }
這是一個 Dumb 的 ThemeSwitch
。新建一個 src/containers/ThemeSwitch.js
:
import { connect } from 'react-redux' import ThemeSwitch from '../components/ThemeSwitch' const mapStateToProps = (state) => { return { themeColor: state.themeColor } } const mapDispatchToProps = (dispatch) => { return { onSwitchColor: (color) => { dispatch({ type: 'CHANGE_COLOR', themeColor: color }) } } } export default connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch)
這是一個 Smart 的 ThemeSwitch
。而後用一個 Smart 的 Content
去使用它,新建 src/containers/Content.js
:
import React, { Component } from 'react' import PropTypes from 'prop-types' import ThemeSwitch from './ThemeSwitch' import { connect } from 'react-redux' class Content extends Component { static propTypes = { themeColor: PropTypes.string } render () { return ( <div> <p style={{ color: this.props.themeColor }}>React.js 小書內容</p> <ThemeSwitch /> </div> ) } } const mapStateToProps = (state) => { return { themeColor: state.themeColor } } export default connect(mapStateToProps)(Content)
刪除 src/ThemeSwitch.js
和 src/Content.js
,在 src/index.js
中直接使用 Smart 組件:
... import Header from './containers/Header' import Content from './containers/Content' ...
這樣就把這種業務場景下的 Smart 和 Dumb 組件分離開來了:
src
├── components
│ ├── Header.js
│ └── ThemeSwitch.js
├── containers
│ ├── Content.js
│ ├── Header.js
│ └── ThemeSwitch.js
└── index.js
若是產品場景要求 Content
可能會被複用,那麼 Content
就要是 Dumb 的。那麼 Content
的之下的子組件 ThemeSwitch
就必定要是 Dumb,不然 Content
就無法複用了。這就意味着 ThemeSwitch
不能 connect
,即便你 connect
了,Content
也不能使用你 connect
的結果,由於 connect
的結果是個 Smart 組件。
這時候 ThemeSwitch
的數據、onSwitchColor
函數只能經過它的父組件傳進來,而不是經過 connect
得到。因此只能讓 Content
組件去 connect
,而後讓它把數據、函數傳給 ThemeSwitch
。
這種場景下的改造留給你們作練習,最後的結果應該是:
src
├── components
│ ├── Header.js
│ ├── Content.js
│ └── ThemeSwitch.js
├── containers
│ ├── Header.js
│ └── Content.js
└── index.js
能夠看到對複用性的需求不一樣,會致使咱們劃分組件的方式不一樣。
根據是否須要高度的複用性,把組件劃分爲 Dumb 和 Smart 組件,約定俗成地把它們分別放到 components
和 containers
目錄下。
Dumb 基本只作一件事情 —— 根據 props
進行渲染。而 Smart 則是負責應用的邏輯、數據,把全部相關的 Dumb(Smart)組件組合起來,經過 props
控制它們。
Smart 組件可使用 Smart、Dumb 組件;而 Dumb 組件最好只使用 Dumb 組件,不然它的複用性就會喪失。
要根據應用場景不一樣劃分組件,若是一個組件並不須要太強的複用性,直接讓它成爲 Smart 便可;不然就讓它成爲 Dumb 組件。
還有一點要注意,Smart 組件並不意味着徹底不能複用,Smart 組件的複用性是依賴場景的,在特定的應用場景下是固然是能夠複用 Smart 的。而 Dumb 則是能夠跨應用場景複用,Smart 和 Dumb 均可以複用,只是程度、場景不同。