快速理解react-redux

react-redux react和redux的结合

简述

相信不少前端开发者都据说或使用过react-redux,我曾写过一篇关于快速理解redux的文章,虽然说是快速理解,但实际上更应该叫作复习redux吧。本文也只是讲述react-redux的思想及原理,对于细节实现则不赘述。javascript


1、初始化工程

  • 咱们先create-react-app新建一个react项目,而后安装第三方库cnpm install --save prop-types
  • 咱们在src目录下新建3个文件,Header.js、Content.js、ThemeSwitch.js
  • 组件结构是这样的
    组建结构
  • 运行起来是这样的
    图片描述

2、结合context和store

  • 咱们先构建store,而且把它放在Index组件的context里面,那样Index如下的全部组件均可以使用store了。前端

    class Index extends Component {
      static childContextTypes = {
        store: PropTypes.object
      }
    
      getChildContext () {
        return { store }
      }
    
      render () {
        return (
          <div>
            <Header />
            <Content />
          </div>
        )
      }
    }
  • 咱们修改Header,Content,ThemeSwitch,定义一个函_updateThemeColor在componentWillMount中调用
  • 在该函数中获取Index组件context里的store,而且将store里的colorState这只为本身文本的颜色
  • 咱们再修改ThemeSwitch,使按钮绑定事件,点击后执行dispatch修改store里的state
  • 此时咱们点击按钮后,store里的数据确实改变了,但是页面却没有改变,为什么?java

    • 由于咱们忽略了subscribe,使得dispatch后_updateThemeColor函数并未执行
  • 咱们分别给 Header.js、Content.js、ThemeSwitch.js 的 componentWillMount 生命周期都加上监听数据变化从新渲染的代码
  • 代码以下,仅以ThemeSwitch为例,其余文件相似react

    class ThemeSwitch extends Component {
      static contextTypes = {
        store: PropTypes.object
      }
    
      constructor () {
        super()
        this.state = { themeColor: '' }
      }
    
      componentWillMount () {
        const { store } = this.context
        this._updateThemeColor()
        store.subscribe(() => this._updateThemeColor())
      }
    
      _updateThemeColor () {
        const { store } = this.context
        const state = store.getState()
        this.setState({ themeColor: state.themeColor })
      }
    
      // dispatch action 去改变颜色
      handleSwitchColor (color) {
        const { store } = this.context
        store.dispatch({
          type: 'CHANGE_COLOR',
          themeColor: color
        })
      }
    
      render () {
        return (
          <div>
            <button
              style={{ color: this.state.themeColor }}
              onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button>
            <button
              style={{ color: this.state.themeColor }}
              onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button>
          </div>
        )
      }
    }

到这里,咱们已经把react-redux的骨架搭建起来了git


3、connect 和 mapStateToProps

咱们观察刚刚写的组件,他们存在两个问题npm

  • 有大量重复逻辑redux

    • 使用Connect高阶组件解决
  • 对context依赖过强,可复用性太低segmentfault

    • 使用mapStateToProps解决

使用高阶组件

咱们须要高阶组件帮助咱们从 context 取数据,咱们也须要写 Dumb(纯) 组件帮助咱们提升组件的复用性。因此咱们尽可能多地写 Dumb 组件,而后用高阶组件把它们包装一层,高阶组件和 context 打交道,把里面数据取出来经过 props 传给 Dumb 组件。app

高阶组件

这个高阶组件被其铭文Connect,由于他能够把 Dunb组件context数据 connect 起来less

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export const connect = (mapStateToProps) => (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    render () {
      const { store } = this.context
      let stateProps = mapStateToProps(store.getState())
      // {...stateProps} 意思是把这个对象里面的属性所有经过 `props` 方式传递进去
      return <WrappedComponent {...stateProps} />
    }
  }

  return Connect
}

咱们将新建一个react-redux文件,将Connect放进去
咱们在定义 mapStateToProps函数 使它接收某参数,返回相应的数据。由于不一样的组件须要store中不一样的数据

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

此时,Header 删掉了大部分关于 context 的代码,它除了 props 什么也不依赖,它是一个 Pure Component,而后经过 connect 取得数据。咱们不须要知道 connect 是怎么和 context 打交道的,只要传一个 mapStateToProps 告诉它应该怎么取数据就能够了。
此时,Connect还未能监听渲染,咱们须要在Connect中建立渲染函数而且在componentWillMount中添加渲染函数

export const connect = (mapStateToProps) => (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(store.getState(), this.props) // 额外传入 props,让获取数据更加灵活方便
      this.setState({
        allProps: { // 整合普通的 props 和从 state 生成的 props
          ...stateProps,
          ...this.props
        }
      })
    }

    render () {
      return <WrappedComponent {...this.state.allProps} />
    }
  }

  return Connect
}

如今已经很不错了,Header.js 和 Content.js 的代码都大大减小了,而且这两个组件 connect 以前都是 Dumb 组件。接下来会继续重构 ThemeSwitch。


mapDispatchToProps

到目前为止,咱们每次在更改数据时,都要用过store.dispatch修改。
为了使组件更 Dunb(纯) 咱们对Connect和ThemeSwitch增长一个mapDispatchToProps 函数,使ThemeSwitch组件摆脱对store.dispatch的依赖

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
}
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 的。


5、Provider

咱们要把全部和context有关的东西都分离出去,如今只有Index组件是被污染的
因此,咱们在react-redux中新增Provider类,让它包裹成为Index的高阶函数,包裹index
使组件结构图以下
Provider
代码以下

export class Provider extends Component {
  static propTypes = {
    store: PropTypes.object,
    children: PropTypes.any
  }

  static childContextTypes = {
    store: PropTypes.object
  }

  getChildContext () {
    return {
      store: this.props.store
    }
  }

  render () {
    return (
      <div>{this.props.children}</div>
    )
  }
}
...
// 头部引入 Provider
import { Provider } from './react-redux'
...

// 删除 Index 里面全部关于 context 的代码
class Index extends Component {
  render () {
    return (
      <div>
        <Header />
        <Content />
      </div>
    )
  }
}

// 把 Provider 做为组件树的根节点
ReactDOM.render(
  <Provider store={store}>
    <Index />
  </Provider>,
  document.getElementById('root')
)

此时,咱们就把全部关于 context 的代码从组件里面删除了。


6、react-redux总结

redux的思想就是有条理地,有规则地修改共享数据。而react里恰好有 context 这个东西能够被某组件如下的全部组件共享,为了在react中优雅的修改context,react-redux就诞生了。它经过高阶函数(Connect),纯函数(mapStateToProps, mapDispatchToProps) 使咱们在编写组件时彻底不用接触context相关内容,只经过ConnectDumb组件Context数据 链接起来便可。


参考

完整代码

make-react-redux

本文若是有错,欢迎指出

相关文章
相关标签/搜索