文章首发于我的博客 https://github.com/mcuking/bl...

相干代码请查阅 https://github.com/mcuking/bl...

本讲次要解决如何在 react 中更优雅的应用 redux,即实现 react-redux

Provider

在实现 react-redux 之前,咱们首先须要理解 react 的 context 机制。当须要将某个数据设置为全局,即可应用 context 在父组件申明,这样其上面的所有子组件都能够获取到这个数据。

基于 context 机制,咱们定义一个 Provider,作为利用的一级组件,专门负责将传入的 store 放到 context 里,所有子组件均能够间接获取 store,并不渲染任何货色。

// Provider 负责将store放到context里,所有子组件均能够间接获取storeexport class Provider extends Component {  // 应用context须要应用propType进行校验  static childContextTypes = {    store: PropTypes.object  };  constructor(props, context) {    super(props, context);    this.store = props.store; // 将传进来的store作为自身的store进行治理  }  // 将传进来的store放入全局context,使得上面的所有子组件均可取得store  getChildContext() {    return { store: this.store };  }  render() {    return this.props.children;  }}

对应业务代码如下:

import React from 'react';import ReactDOM from 'react-dom';import { createStore } from './mini-redux';import { Provider } from './mini-react-redux';import { counter } from './index.redux';import App from './App';const store = createStore(counter);ReactDOM.render(  <Provider store={store}>    <App />  </Provider>,  document.getElementById('root'));

connect

connect 负责连贯组件,将 redux 中的数据传入组件的属性里,因而须要实现上面两件事:

  1. 负责接管一个组件,并将组件对应全局 state 里的一些数据放进去,返回一个新组件
  2. 数据变动时,可能告诉组件

因而 connect 自身是一个高阶组件,首先接管上面两个参数,而后再接管一个组件:

  • mapStateToProps,是一个函数,入参为全局 state,并返回全局 state 中组件须要的的数据,代码如下:
const mapStateToProps = state => {  return {    num: state  };};
  • mapDispatchToProps,是一个对象,对象外面为 action(用来扭转全局状态的对象)的生成函数,代码如下:
const mapDispatchToProps = {  buyHouse,  sellHouse};// action creatorexport function buyHouse() {  return { type: BUY_HOUSE };}export function sellHouse() {  return { type: SELL_HOUSE };}

第一步,咱们将 mapStateToProps 的返回值,即组件须要的全局状态 state 中的某个状态,以参数的模式传给新构建的组件,代码如下:

export const connect = (  mapStateToProps = state => state,  mapDispatchToProps = {}) => WrapComponent => {  // 高阶函数,间断两个箭头函数意味着嵌套两层函数  return class ConnectComponent extends Component {    // 应用context须要应用propType进行校验    static contextTypes = {      store: PropTypes.object    };    constructor(props, context) {      super(props, context);      this.state = {        props: {}      };    }    componentDidMount() {      this.update();    }    // 获取mapStateToProps返回值,放入到this.state.props    update() {      const { store } = this.context; // 获取放在全局context的store      const stateProps = mapStateToProps(store.getState());      this.setState({        props: {          ...this.state.props,          ...stateProps        }      });    }    render() {      return <WrapComponent {...this.state.props} />;    }  };};

第二步,咱们须要将 mapDispatchToProps 这个对象中批改全局状态的办法传入给组件,然而间接将相似 buyHouse 办法传给组件,并在组件中执行 buyHouse()办法并不能扭转全局状态。

联想到上一讲 redux 中,批改全局状态,须要应用 store 的 dispatch 办法,dispatch 对应代码如下:

function dispatch(action) {  // reducer依据老的state和action计算新的state  currentState = reducer(currentState, action);  // 当全局状态变动时,执行传入的监听函数  currentListeners.forEach(v => v());  return action;}

其中须要内部传入 action,即一个对象,例如{type: BUY_HOUSE}。因而咱们须要将 buyHouse 办法的返回值 action 对象,传给 store.dispatch 办法,执行后能力扭转全局状态。对应代码如下:

buyHouse = () => store.dispatch(buyHouse());

对此,咱们封装一个办法 bindActionCreators,入参为 mapDispatchToProps 和 store.dispatch,返回相似 buyHouse = () => store.dispatch(buyHouse())的办法的汇合,即应用 dispatch 将 actionCreator 的返回值包一层,代码如下:

// 将 buyHouse(...arg) 转换为 (...arg) => store.dispatch(buyHouse(...arg))function bindActionCreator(creator, dispatch) {  return (...arg) => dispatch(creator(...arg)); // 参数arg透传}// creators 示例 {buyHouse, sellHouse, buyHouseAsync}export function bindActionCreators(creators, dispatch) {  let bound = {};  for (let v in creators) {    bound[v] = bindActionCreator(creators[v], dispatch);  }  return bound;}

因而,咱们就能够第一步的根底上,将 store.dispatch 包装后的 actionCreator 汇合对象,传给组件,代码如下:

export const connect = (  mapStateToProps = state => state,  mapDispatchToProps = {}) => WrapComponent => {  // 高阶函数,间断两个箭头函数意味着嵌套两层函数  return class ConnectComponent extends Component {    // 应用context须要应用propType进行校验    static contextTypes = {      store: PropTypes.object    };    constructor(props, context) {      super(props, context);      this.state = {        props: {}      };    }    componentDidMount() {      const { store } = this.context;      store.subscribe(() => this.update()); // 每当全局状态更新,均须要更新传入组件的状态和办法      this.update();    }    // 获取mapStateToProps的返回值 和 mapDispatchToProps,放入到this.state.props    update() {      const { store } = this.context; // 获取放在全局context的store      const stateProps = mapStateToProps(store.getState());      // 办法不能间接传,因为须要dispatch      // 例如间接执行addHouse()毫无意义,须要buyHouse = () => store.dispatch(addHouse())才有意义      // 其实就应用diapatch将actionCreator包了一层      const dispatchProps = bindActionCreators(        mapDispatchToProps,        store.dispatch      );      this.setState({        props: {          ...this.state.props,          ...stateProps,          ...dispatchProps        }      });    }    render() {      return <WrapComponent {...this.state.props} />;    }  };};

留神,除了将 dispatchProps 传给组件之外,下面代码还在组件的 componentDidMount 生命周期中,将 update 函数设置为监听函数,即

store.subscribe(() => this.update())

从而,每当全局状态发生变化,都会从新获取最新的传入组件的状态和办法,实现组件状态与全局状态同步的成果。

相干文章如下:

  • mini-redux 实现原理解说 第一讲
  • mini-redux 实现原理解说 第二讲
  • mini-redux 实现原理解说 第三讲
  • mini-redux 实现原理解说 第四讲