本文参考 《react.js 小书》

一、目的

之前写过一篇文章,主要是看 react.js 小书的时候,也思考了一下 redux 这种模式(或者说 flux 这种模式)所解决的问题的痛点。

至少应该知道,他们为什么会产生,以及产生的过程中逐步解决了什么问题。

感兴趣的可以全看看 react.js 小书。

之前的文章主要是在完全不使用 react 的情况下写一个 redux 模式的应用,基本的内容如下链接:

有了这个前提,又考虑了如何在 React 组件中使用 redux 模式。

如果直接使用 reduxreact-redux 这两个库的话,应该都知道下面几个概念:

  • Provider
  • connect
  • createStore
  • mapStateToProps
  • mapDispatchToProps
  • ...

二、前提

1、createStore

实现一个简单的 react-redux ,首先需要有 createStore 的基本逻辑,能够进行观察。

下面的 createStore 就是之前文章中写出来的:

/**
 * createStore
 * - 用来创建 store
 * - 实现了观察者模式
 * - 对 reducer 进行了处理
 * - 默认会首先 dispatch 一次空对象,也就是通过 reducer 生成初始化的数据
 * @param {*} reducer
 */
function createStore(reducer) {
  let state = null; // 初始化 state 是 null
  const listeners = []; // 监听者是空
  const subscribe = (listener) => listeners.push(listener); // 如果有监听者,则将其 push 进数组
  const getState = () => state; // 返回 state
  const dispatch = (action) => {
      // 触发 action 
      state = reducer(state, action); // 使用 reducer 覆盖原来的 state
      listeners.forEach((listener) => listener());// 对所有监听者进行更新操作
  };
  dispatch({});// 默认进行一次初始化state
  return {getState, dispatch, subscribe}; // 返回 store 对象
}

2、reducer

面对业务逻辑还需要一个 reducer,比如我直接用的 react.js 小书中的 themeReducer

/**
 * themeReducer 
 * @param {*} state 
 * @param {*} action 
 */
function themeReducer(state, action) {
  // 默认 color red
  if (!state) {
    return {themeColor: 'red'};
  }
  // 现在只写了一个 action
  switch (action.type) {
    case 'CHANGE_COLOR': 
      return {
        ...state,
        themeColor: action.data
      };
    default:
      return state;
  }
}

三、实现业务逻辑

1、connect 高阶组件

实际上 connect 就是一个高阶组件,它将组件包装之后拿到一个能够在组件中直接获取 context 的 state 的组件:

connect 的实现方案在注释中写的很明显:

connect 的作用很明显,把一个组件进行包装之后,能够直接拿到 store 中的 state,此时,state 是作为
props 下发的,当然,组件本身接收的 props 不会有任何的影响。

只实现了 mapToStateProps 和 mapToDispatchProps ,需要引入 prop-types

/**
 * 高阶组件 connect
 * @param {*} mapStateToProps 
 * @param {*} mapDispatchToProps 
 */
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
    class Connect extends Component {
        constructor() {
            super();
            this.state = {
                allProps: {}
            };
        }
        // context 约束必须
        static contextTypes = {
            store: PropTypes.object
        };
        // 组件挂在前需要执行的操作
        componentWillMount() {
            const {store} = this.context; // 从上下文中获取 store 该 store 是从根组件传递过来的
            this._updateProps();// 初始化执行一次 updateProps
            store.subscribe(() => this._updateProps()); // 加入观察者
        }
        // 用于更新 props 
        _updateProps() {
            const {store} = this.context;
            let stateProps = mapStateToProps
                ? mapStateToProps(store.getState(), this.props)
                : {}; // 主要用来进行 store 的 state 的获取
            let dispatchProps = mapDispatchToProps
                ? mapDispatchToProps(store.dispatch, this.props)
                : {} // 用来 dispatch 的时候获取 store 的 dispatch
            // 整合普通的 props 和 从 state 生成的 props 
            // 作为完整的 state 返回,这样在子组件中就能够通过 props 获取内容
            this.setState({
                allProps: { 
                    ...stateProps,
                    ...dispatchProps,
                    ...this.props
                }
            })
        }
        render() {
            return <WrappedComponent {...this.state.allProps}/>
        }
    }
    // 返回高阶组件
    return Connect;
}

四、使用 connect

有了 connect 就能够在组件中去使用:

下面代码和 react-redux 基本上是一样的

const mapStateToProps = (state) => {
    return {
        themeColor:state.themeColor
    };
}

Content = connect(mapStateToProps)(Content);