之前我们实现了 redux 的功能,这次我们来实现一下配合 redux 开发中经常会用到的一个库—— react-redux。本文不会详细介绍 react-redux 的使用,另外需要了解 Context API, 再看此文就很容易理解了。
前言
可以看看我之前写的几篇文章
react-redux 基本使用
用个简单的加减数字作例子, 把代码贴出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   | import { createStore } from 'redux'
 
  const ADD_NUM = 'ADD_NUM' const DESC_NUM = 'DESC_NUM'
 
  export function addNumAction() {   return {     type: ADD_NUM,   } }
  export function reduceNumAction() {   return {     type: DESC_NUM,   } }
  const defaultState = {   num: 0, }
 
  const reducer = (state = defaultState, action) => {   switch (action.type) {     case ADD_NUM:       return { num: state.num + 1 }     case DESC_NUM:       return { num: state.num - 1 }     default:       return state   } }
  const store = createStore(reducer)
  export default store
  | 
index.js
1 2 3 4 5 6 7 8 9 10 11 12
   | import React from 'react' import ReactDOM from 'react-dom' import Demo from './Demo' import { Provider } from 'react-redux' import store from './redux.js'
  ReactDOM.render(   <Provider store={store}>     <Demo />   </Provider>,   document.getElementById('root') )
   | 
Demo.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
   | import React, { Component } from 'react' import { connect } from 'react-redux' import { addNumAction, reduceNumAction } from './redux.js'
  class Demo extends Component {   render() {     console.log(this.props)     return (       <div>         <p>{this.props.num}</p>         <button onClick={this.props.addNum}>增加1</button>         <button onClick={this.props.reduceNum}>减少1</button>       </div>     )   } }
  const mapStateToProps = state => {   return {     num: state.num,   } }
  const mapDispatchToProps = dispatch => {   return {     addNum() {       const action = addNumAction()       dispatch(action)     },     reduceNum() {       const action = reduceNumAction()       dispatch(action)     },   } }
  export default connect(mapStateToProps, mapDispatchToProps)(Demo)
  | 
就可以实现 num 的增减:

其实一个简单的 react-redux, 主要也就是实现 connect 和 Provider 的基本功能
- connect:可以把 state 和 dispatch 绑定到 react 组件,使得组件可以访问到 redux 的数据
 - Provider:提供的是一个顶层容器的作用,实现 store 的上下文传递
 
使用旧版 Context API 实现
实现 Provider
首先我们看它的用法,就知道它不是一个函数,而是一个组件:
1 2 3
   | <Provider store={store}>     <Demo /> </Provider>
  | 
React 的 Context API 提供了一种通过组件树传递数据的方法,无需在每个级别手动传递 props 属性。
Provider 的实现比较简单,核心就是把 store 放到 context 里面,所有的子元素可以直接取到 store。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | export class Provider extends Component {   static childContextTypes = {     store: PropTypes.object,   }
    constructor(props) {     super(props)     this.store = props.store    }
    getChildContext() {     return { store: this.store }   }
    render() {     return this.props.children    } }
  | 
还有个地方大家知道就好,两种写法一样的,对 context type 的约束
1 2 3 4 5 6
   | export class Provider extends Component {    } Provider.childContextTypes = {   store: PropTypes.object, }
  | 
实现 connect
connect 用法
1
   | export default connect(mapStateToProps, mapDispatchToProps)(Demo)
   | 
connect 是一个高阶组件,就是以组件作为参数,返回一个组件。
connect 负责连接组件,给到 redux 的数据放到组件的属性里
- 负责接收一个组件,把 state 的一些数据放进去,返回一个组件
 - 数据变化的时候,能够通知组件(需要进行监听)
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
   | export function connect(mapStateToProps, mapDispatchToProps) {   return function (WrapComponent) {     return class ConnectComponent extends Component {       static contextTypes = {         store: PropTypes.object,       }
        constructor(props) {         super(props)         this.state = {           props: {},         }       }
        componentDidMount() {         const { store } = this.context         store.subscribe(() => this.update())          this.update()       }
        update() {         const { store } = this.context         let stateToProps = mapStateToProps(store.getState())          let dispatchToProps = mapDispatchToProps(store.dispatch) 
          this.setState({           props: {             ...this.state.props,             ...stateToProps,             ...dispatchToProps,           },         })       }
        render() {         return <WrapComponent {...this.state.props} />       }     }   } }
  | 
这样 connect 就实现了,但还有一个问题,像上面的例子,我们其实可以直接传入 action creators, 而不用自己定义函数传入 dispatch
1
   | export default connect(mapStateToProps, { addNumAction, reduceNumAction })(Demo)
  | 
调用的时候:
1 2
   | <button onClick={this.props.addNumAction}>增加1</button> <button onClick={this.props.reduceNumAction}>减少1</button>
  | 
那它的 dispatch 哪里来的,其实是用了 redux 的 bindActionCreators 函数,在我介绍 redux 的文章有提到,它作用是将 actionCreator 转化成 dispatch 形式,即
1
   | { addNumAction }  =>  (...args) => dispatch(addNumAction(args))
  | 
所以我们需要再更改 connect 函数,同时,这次我们用箭头函数的形式简化代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
   | import { bindActionCreators } from 'redux'
  export const connect = (mapStateToProps, mapDispatchToProps) => WrapComponent => {   return class ConnectComponent extends Component {     static contextTypes = {       store: PropTypes.object,     }
      constructor(props) {       super(props)       this.state = {         props: {},       }     }
      componentDidMount() {       const { store } = this.context       store.subscribe(() => this.update())       this.update()     }
      update() {       const { store } = this.context       let stateToProps = mapStateToProps(store.getState())       let dispatchToProps       if (typeof mapDispatchToProps === 'function') {         dispatchToProps = mapDispatchToProps(store.dispatch)       } else {                  dispatchToProps = bindActionCreators(mapDispatchToProps, store.dispatch)       }
        this.setState({         props: {           ...this.state.props,           ...stateToProps,           ...dispatchToProps,         },       })     }
      render() {       return <WrapComponent {...this.state.props} />     }   } }
  | 
以上,我们实现了最基本版的 react-redux,然后接下来,我们用新版的 Context API 再写一次
使用新版 Context API 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
   | import React, { Component } from 'react' import { bindActionCreators } from 'redux'
  const StoreContext = React.createContext(null)
 
  export class Provider extends Component {   render() {     return <StoreContext.Provider value={this.props.store}>{this.props.children}</StoreContext.Provider>   } }
  export function connect(mapStateToProps, mapDispatchToProps) {   return function (WrapComponent) {     class ConnectComponent extends Component {       constructor(props) {         super(props)         this.state = {           props: {},         }       }
        componentDidMount() {         const { store } = this.props         store.subscribe(() => this.update())         this.update()       }
        update() {         const { store } = this.props         let stateToProps = mapStateToProps(store.getState())         let dispatchToProps         if (typeof mapDispatchToProps === 'function') {           dispatchToProps = mapDispatchToProps(store.dispatch)         } else {                      dispatchToProps = bindActionCreators(mapDispatchToProps, store.dispatch)         }
          this.setState({           props: {             ...this.state.props,             ...stateToProps,             ...dispatchToProps,           },         })       }
        render() {         return <WrapComponent {...this.state.props} />       }     }
      return () => <StoreContext.Consumer>{value => <ConnectComponent store={value} />}</StoreContext.Consumer>   } }
  |