在重构 ThemeSwitch 的时候我们发现,ThemeSwitch 除了需要 store 里面的数据以外,还需要 storedispatch

    1. ...
    2. // dispatch action 去改变颜色
    3. handleSwitchColor (color) {
    4. const { store } = this.context
    5. store.dispatch({
    6. type: 'CHANGE_COLOR',
    7. themeColor: color
    8. })
    9. }
    10. ...

    目前版本的 connect 是达不到这个效果的,我们需要改进它。

    想一下,既然可以通过给 connect 函数传入 mapStateToProps 来告诉它如何获取、整合状态,我们也可以想到,可以给它传入另外一个参数来告诉它我们的组件需要如何触发 dispatch。我们把这个参数叫 mapDispatchToProps

    1. const mapDispatchToProps = (dispatch) => {
    2. return {
    3. onSwitchColor: (color) => {
    4. dispatch({ type: 'CHANGE_COLOR', themeColor: color })
    5. }
    6. }
    7. }

    mapStateToProps 一样,它返回一个对象,这个对象内容会同样被 connect 当作是 props 参数传给被包装的组件。不一样的是,这个函数不是接受 state 作为参数,而是 dispatch,你可以在返回的对象内部定义一些函数,这些函数会用到 dispatch 来触发特定的 action

    调整 connect 让它能接受这样的 mapDispatchToProps

    1. export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
    2. class Connect extends Component {
    3. static contextTypes = {
    4. store: PropTypes.object
    5. }
    6. constructor () {
    7. super()
    8. this.state = {
    9. allProps: {}
    10. }
    11. }
    12. componentWillMount () {
    13. const { store } = this.context
    14. this._updateProps()
    15. store.subscribe(() => this._updateProps())
    16. }
    17. _updateProps () {
    18. const { store } = this.context
    19. let stateProps = mapStateToProps
    20. ? mapStateToProps(store.getState(), this.props)
    21. : {} // 防止 mapStateToProps 没有传入
    22. let dispatchProps = mapDispatchToProps
    23. ? mapDispatchToProps(store.dispatch, this.props)
    24. : {} // 防止 mapDispatchToProps 没有传入
    25. this.setState({
    26. allProps: {
    27. ...stateProps,
    28. ...dispatchProps,
    29. ...this.props
    30. }
    31. })
    32. }
    33. render () {
    34. return <WrappedComponent {...this.state.allProps} />
    35. }
    36. }
    37. return Connect
    38. }

    _updateProps 内部,我们把store.dispatch 作为参数传给 mapDispatchToProps ,它会返回一个对象 dispatchProps。接着把 statePropsdispatchPropsthis.props 三者合并到 this.state.allProps 里面去,这三者的内容都会在 render 函数内全部传给被包装的组件。

    另外,我们稍微调整了一下,在调用 mapStateToPropsmapDispatchToProps 之前做判断,让这两个参数都是可以缺省的,这样即使不传这两个参数程序也不会报错。

    这时候我们就可以重构 ThemeSwitch,让它摆脱 store.dispatch

    1. import React, { Component } from 'react'
    2. import PropTypes from 'prop-types'
    3. import { connect } from './react-redux'
    4. class ThemeSwitch extends Component {
    5. static propTypes = {
    6. themeColor: PropTypes.string,
    7. onSwitchColor: PropTypes.func
    8. }
    9. handleSwitchColor (color) {
    10. if (this.props.onSwitchColor) {
    11. this.props.onSwitchColor(color)
    12. }
    13. }
    14. render () {
    15. return (
    16. <div>
    17. <button
    18. style={{ color: this.props.themeColor }}
    19. onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button>
    20. <button
    21. style={{ color: this.props.themeColor }}
    22. onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button>
    23. </div>
    24. )
    25. }
    26. }
    27. const mapStateToProps = (state) => {
    28. return {
    29. themeColor: state.themeColor
    30. }
    31. }
    32. const mapDispatchToProps = (dispatch) => {
    33. return {
    34. onSwitchColor: (color) => {
    35. dispatch({ type: 'CHANGE_COLOR', themeColor: color })
    36. }
    37. }
    38. }
    39. ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch)
    40. export default ThemeSwitch

    光看 ThemeSwitch 内部,是非常清爽干净的,只依赖外界传进来的 themeColoronSwitchColor。但是 ThemeSwitch 内部并不知道这两个参数其实都是我们去 store 里面取的,它是 Dumb 的。这时候这三个组件的重构都已经完成了,代码大大减少、不依赖 context,并且功能和原来一样。


    因为第三方评论工具有问题,对本章节有任何疑问的朋友可以移步到 React.js 小书的论坛 发帖,我会回答大家的疑问。