在重構 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,並且功能和原來一樣。
