手撕源码之—从1实现react-redux

683 阅读3分钟

从0实现react-redux 上一节我们介绍了redux的用法和源码的实现,使用时都要在componentDidMount中订阅,在组件销毁时取消订阅,还需要用boundActionCreators方法对action进行绑定,那么怎么简化这些东西呢? 接下来我们来实现另外一个库react-redux,主要作用就是用来简化在react中使用redux的流程。

我们先用原生的react-redux来改造我们的组件Counter1

  • 引入Providerstore做为属性传给Provider,这样Provider包裹的所有组件都可以拿到store,而不用在每个组件内都引入store
import React from "react";
import ReactDOM from "react-dom";
+import { Provider } from "react-redux";
import store from "./store";

import Counter1 from "./components/Counter1";
import Counter2 from "./components/Counter2";

ReactDOM.render(
+  <Provider store={store}>
    <Counter1 />
    <Counter2 />
+  </Provider>,
  document.getElementById("root")
);
  • 引入connect方法,用来将store里的state、定义的action和组件进行绑定,注入组件的props中。
import React, { Component } from "react";
import actions from "../store/actions/counter1";
// 引入connect方法
import { connect } from "react-redux";
class Counter1 extends Component {
  render() {
    // 连接后的组件,可以在props中取到 属性
    let { number, add1, minus1 } = this.props;
    return (
      <div>
        <p>{number}</p>
        <button onClick={add1}>+</button>
        <button onClick={minus1}>-</button>
        <button onClick={() => setTimeout(() => add1(), 1000)}>1秒后加1</button>
      </div>
    );
  }
}

let mapStateToProps = (state) => state.counter1;
// 将组件和状态、action进行连接
export default connect(mapStateToProps, actions)(Counter1);

是不是简单多了,这样就不用在每个组件中引入store,绑定action,定义订阅和取消订阅的函数了,那内部是怎么实现的呢?

实现Provider组件

  • Provider就是对React context的一层封装,将store作为value提供给所有子组件
import React from "react";
import ReactReduxContext from "./ReactReduxContext";

export default function (props) {
  return (
    <ReactReduxContext.Provider value={{ store: props.store }}>
      {props.children}
    </ReactReduxContext.Provider>
  );
}
  • 创建ReactReduxContext对象
import React from "react";
export const ReactReduxContext = React.createContext(null);
export default ReactReduxContext;

类组件 实现connect方法

  • connect方法是一个高阶函数,传入mapStateToPropsactions,返回一个高阶组件,在高阶组件中对原有的组件进行封装,注入新的属性和方法。
    • 1、首先通过context获取 Provider组件提供的 store
    • 2、在componentDidMount中用获取的store订阅state的状态,当state发生变化时,调用forceUpdate方法重新渲染组件
    • 3、在componentWillUnmount组件将要卸载时取消订阅
    • 4、在render中拿到 store里的state, 再通过传入的mapStateToProps方法获取到组件依赖的状态stateProps, 将这些状态作为props属性注入到OldComponent组件中
    • 5、通过bindActionCreators方法,将 传入的actionsstore里的dispatch方法进行绑定,绑定的结果dispatchProps也作为props属性注入到OldComponent组件中
  • 这样在OldComponent中就可以通过props属性拿到store里的state和绑定后的action进行使用
  • 当调用action时,会向store中派发事件,通过reducer函数更新状态,再通过组件订阅的方法更新组件状态
import React, { useContext } from "react";
import { bindActionCreators } from "../redux";
import ReactReduxContext from "./ReactReduxContext";

// 类组件实现
function connect(mapStateToProps, actions) {
  return function (OldComponent) {
    return class NewComponent extends React.Component {
      // 获取Provider提供的store
      static contextType = ReactReduxContext;
      constructor(props, context) {
        super(props, context);
      }
      componentDidMount() {
      	// 订阅状态更改,重新渲染组件
        this.unsubscribe = this.context.store.subscribe(() =>
          this.forceUpdate()
        );
      }
      componentWillUnmount() {
        // 组件卸载,取消订阅
        this.unsubscribe();
      }
      render() {
      	// 获取store中的状态
        const { store } = this.context;
        const state = store.getState();
        // 获取组件依赖的状态
        const stateProps = mapStateToProps(state);
        // 绑定action
        const dispatchProps = bindActionCreators(actions, store.dispatch);
        return (
          <OldComponent {...this.props} {...stateProps} {...dispatchProps} />
        );
      }
    };
  };
}
export default connect;

代码地址

如果这篇文章对你有帮助,请帮我点个赞吧