在重構 ThemeSwitch
的時候我們發現,ThemeSwitch
除了需要 store
里面的數據以外,還需要 store
來 dispatch
:
... // dispatch action 去改變顏色 handleSwitchColor (color) { const { store } = this.context store.dispatch({ type: 'CHANGE_COLOR', themeColor: color }) } ...
目前版本的 connect
是達不到這個效果的,我們需要改進它。
想一下,既然可以通過給 connect
函數傳入 mapStateToProps
來告訴它如何獲取、整合狀態,我們也可以想到,可以給它傳入另外一個參數來告訴它我們的組件需要如何觸發 dispatch
。我們把這個參數叫 mapDispatchToProps
:
const mapDispatchToProps = (dispatch) => { return { onSwitchColor: (color) => { dispatch({ type: 'CHANGE_COLOR', themeColor: color }) } } }
和 mapStateToProps
一樣,它返回一個對象,這個對象內容會同樣被 connect
當作是 props
參數傳給被包裝的組件。不一樣的是,這個函數不是接受 state
作為參數,而是 dispatch
,你可以在返回的對象內部定義一些函數,這些函數會用到 dispatch
來觸發特定的 action
。
調整 connect
讓它能接受這樣的 mapDispatchToProps
:
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => { class Connect extends Component { static contextTypes = { store: PropTypes.object } constructor () { super() this.state = { allProps: {} } } componentWillMount () { const { store } = this.context this._updateProps() store.subscribe(() => this._updateProps()) } _updateProps () { const { store } = this.context let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props) : {} // 防止 mapStateToProps 沒有傳入 let dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch, this.props) : {} // 防止 mapDispatchToProps 沒有傳入 this.setState({ allProps: { ...stateProps, ...dispatchProps, ...this.props } }) } render () { return <WrappedComponent {...this.state.allProps} /> } } return Connect }
在 _updateProps
內部,我們把store.dispatch
作為參數傳給 mapDispatchToProps
,它會返回一個對象 dispatchProps
。接着把 stateProps
、dispatchProps
、this.props
三者合並到 this.state.allProps
里面去,這三者的內容都會在 render
函數內全部傳給被包裝的組件。
另外,我們稍微調整了一下,在調用 mapStateToProps
和 mapDispatchToProps
之前做判斷,讓這兩個參數都是可以缺省的,這樣即使不傳這兩個參數程序也不會報錯。
這時候我們就可以重構 ThemeSwitch
,讓它擺脫 store.dispatch
:
import React, { Component } from 'react' import PropTypes from 'prop-types' import { connect } from './react-redux' 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> ) } } const mapStateToProps = (state) => { return { themeColor: state.themeColor } } const mapDispatchToProps = (dispatch) => { return { onSwitchColor: (color) => { dispatch({ type: 'CHANGE_COLOR', themeColor: color }) } } } ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch) export default ThemeSwitch
光看 ThemeSwitch
內部,是非常清爽干凈的,只依賴外界傳進來的 themeColor
和 onSwitchColor
。但是 ThemeSwitch
內部並不知道這兩個參數其實都是我們去 store
里面取的,它是 Dumb 的。這時候這三個組件的重構都已經完成了,代碼大大減少、不依賴 context,並且功能和原來一樣。