深入浅出redux,react-redux源码

197 阅读17分钟

深入浅出react-redux和redux源码

概述

现在前端框架普遍以组件为基本组成单位,react也不例外,所以学习react组件之间怎么传递数据就显得非常重要,我们先来了解一下组件中普通的传递数据的方式,再来编写redux和react-redux源码,一步步分析redux是怎么传递数据的。

普通传递数据的方式:props和context

众所周知,redux实现了全局数据分享,能够在多个组件之间共享数据,但是redux并不是专门为react准备的,在react中使用redux会稍显麻烦,所以我们可以使用react-redux来以一种更优雅的方式实现redux中的发布订阅设计模式

props和context是基本的传递数据的方式,react-redux也是基于这两者实现的,并且配合redux共享数据,我们会用几个小案例剖析props和context是怎么传递数据的,流程可能会稍显冗长,但是对于我们接下来分析react-redux和redux源码是有很大的帮助的。

props属性传递

这应该是最常用的的一种方式了,调取子组件的时候,把信息基于属性的方式传递给子组件(子组件PROPS中存储传递的信息),这种方式只能父组件把信息传递子组件,子组件无法直接的把信息传递给父组件,也就是属性传递信息是单向传递的。

简单的代码示例:

import React from "react";

export default class Panel extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
  }
  //可以在组件上通过添加属性的方式传递props
  render() {
    return (
      <section className="Panel">
        <h1>Panel</h1>
        <Head counter={this.state.counter} />
        <Body add={this.add} minus={this.minus} />
      </section>
    );
  }

  add = () => {
    this.setState({ counter: this.state.counter + 1 });
  };
  minus = () => {
    this.setState({ counter: this.state.counter - 1 });
  };
}

// 子组件Head
class Head extends React.Component {
  render() {
    return (
      <section className="Head">
        <h2>Head</h2>
        <p>{this.props.counter}</p>
      </section>
    );
  }
}

// 子组件Body
class Body extends React.Component {
  render() {
    return (
      <section className="Body">
        <h2>Body</h2>
        <button onClick={this.props.add}>add</button>
        <button onClick={this.props.minus}>minus</button>
      </section>
    );
  }
}

实际效果: props属性 head中的属性,传递进来了counter值: Body中的属性,传递进来了add和minus方法:

context上下文传递

父组件先把需要给后代元素(包括孙子元素)使用的信息都设置好(设置在上下文中),后代组件需要用到父组件中的信息,主动去父组件中调取使用即可。

  1. 新版本的context
    新版本的React context使用了Provider和Customer模式,和react-redux的模式非常像。在顶层的Provider中传入value,在子孙级的Consumer中获取该值,并且能够传递函数,用来修改context。 代码示例:
import React, { useContext } from "react";

const Context = React.createContext();
const Provider = Context.Provider;
const Consumer = Context.Consumer;

export default class Panel extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
  }

  render() {
    return (
      <section className="Panel">
        <h1>Panel</h1>
        <Provider
          value={{
            counter: this.state.counter,
            add: this.add,
            minus: this.minus,
          }}
        >
          <Head />
          <Consumer>{(ctx) => <Body ctx={ctx} />}</Consumer>
        </Provider>
      </section>
    );
  }

  add = () => {
    this.setState({ counter: this.state.counter + 1 });
  };
  minus = () => {
    this.setState({ counter: this.state.counter - 1 });
  };
}

// 子组件Head
function Head() {
  const store = useContext(Context);
  let { counter } = store;
  console.log(counter);
  return (
    <section className="Head">
      <h2>Head</h2>
      <p>{counter}</p>
    </section>
  );
}

// 子组件Body
class Body extends React.Component {
  render() {
    const { add, minus } = this.props.ctx;
    return (
      <section className="Body">
        <h2>Body</h2>
        <button onClick={add}>add</button>
        <button onClick={minus}>minus</button>
      </section>
    );
  }
}

Provider中的值,有我们需要的属性: Consumer中的值,将props转变为contenxt,并通过回调函数,返回子组件,并将context的值放到子组件的props属性上: 子组件中的值,有props属性: 可以看出子组件是通过获取Consumer中的context的内容,并转换为自己的props属性

  1. 老版本的context
    • childContextTypes: 根组件中声明,指定context的结构类型,不声明,会报错;
    • getChildContext: 根组件中声明的一个函数,返回一个对象,就是context;
    • contextTypes 子孙组件中声明,指定要接收的context的结构类型,可以只是context的一部分结构,contextTypes 没有定义,context将是一个空对象;
    • this.context 在子孙组件中通过此来获取上下文。

注:从React v15.5开始 ,React.PropTypes 助手函数已被弃用,可使用 prop-types 库 来定义contextTypes,通过“yarn add prop-types”安装即可。
代码示例:

import React from "react";
import PropTypes from "prop-types";

export default class Panel extends React.Component {
// 父组件设置信息
  static childContextTypes = {
   // 设置上下文中信息值的类型
    counter: PropTypes.number,
    add: PropTypes.func,
    minus: PropTypes.func,
  };

  getChildContext() {
   // RETURN的是啥,相当于往上下文中放了啥
    return {
      counter: this.state.counter,
      add: this.add,
      minus: this.minus,
    };
  }
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
  }

  render() {
    return (
      <section className="Panel">
        <h1>Panel</h1>
        <Head />
        <Body />
      </section>
    );
  }

  add = () => {
    this.setState({ counter: this.state.counter + 1 });
  };
  minus = () => {
    this.setState({ counter: this.state.counter - 1 });
  };
}

// 子组件Head
class Head extends React.Component {
  // 子组件主动获取需要的信息
  static contextTypes = {
  // 首先类型需要和设置时候类型一样,否则报错,只需要写你需要用到的。
    counter: PropTypes.number,
  };
  render() {
    console.log(this.context);
    return (
      <section className="Head">
        <h2>Head</h2>
        <p>{this.context.counter}</p>
      </section>
    );
  }
}

// 子组件Body
class Body extends React.Component {
  static contextTypes = {
    add: PropTypes.func,
    minus: PropTypes.func,
  };
  render() {
    return (
      <section className="Body">
        <h2>Body</h2>
        <button onClick={this.context.add}>add</button>
        <button onClick={this.context.minus}>minus</button>
      </section>
    );
  }
}

父组件的信息: 子组件的信息,通过contextTypes获取需要的信息:

属性 VS 上下文

  1. 属性操作起来简单,子组件是被动接收传递的值(组件内的属性是只读的),只能父传子(子传父不行,父传孙也需要处理:父传子,子再传孙);
  2. 上下文操作起来相对复杂一些,子组件是主动获取信息使用的(子组件是可以修改获取到的上下文信息的,但是不会影响到父组件中的信息,其它组件不受影响),一旦父组件设置了上下文信息,它后代组件都可以直接拿来用,不需要一层层的传递

redux

概述:

redux可以应用在任何的项目中(VUE/JQ/RERACT的都可以),react-redux才是专门给react项目提供的方案。

基本流程:

  1. createStore 创建store
  2. reducer 初始化、修改状态函数
  3. getState 获取状态值
  4. dispatch 提交更新
  5. subscribe 变更订阅

redux里面的主要方法

  1. createStore:创建一个store,存储的是各个组件的状态信息,当你传进来的是一个reducer函数,redux里面会先执行一次dispatch,用内部的一个变量state接收reducer函数默认执行的初始值,createStore是典型的发布订阅设计模式,它返回一个对象,包含dispatch,getState,subscribe等方法,并通过闭包保存了两个值,分别是state和listenAry,state保存了最新的状态信息,listenAry保存了subscribe发布的任务。通过这三个方法可以形成一个闭环,保证在更新数据后,重新渲染组件,渲染组件时拿到新的数据
  2. combineReducers:能够组合多个reducer函数,每个reducer函数代表项目中一部分组件的功能,combineReducers参数是多个reducer函数组成的对象,它执行后返回一个函数,这个函数作为新的reducer函数传入createStore,新的reducer执行后会返回一个对象,由以前的单个reducer函数的函数名/函数执行结果组成键值对,描述起来比较绕,但是等一下写完源代码,便会一目了然;
  3. applyMiddleware:是一个增强器,可以提供各种中间件来增强redux的功能,比如logger提供了日志记录功能,thunk提供了实现异步任务的功能,因为入redux只是个纯粹的状态管理器,默认只支持同步,实现异步任务,比如延迟,网络请求,需要thunk。它是一个典型的柯里化函数,接收多个函数作为参数,返回一个新的函数。

现在我们将上面的案例通过redux进行改造,在改造之前,我们先用一张简单的图表来描述整个redux工作流程 代码示例:

import React from "react";
import { createStore, combineReducers, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";

// 这是一个典型的reducer,通过匹配不同的action来返回不同的值
function counterReducer(state = 0, action) {
  switch (action.type) {
    case "add":
      return state + 1;
    case "minus":
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(
  combineReducers({ counterReducer }),
  applyMiddleware(logger, thunk)
);

export default class ReduxPage extends React.Component {
  render() {
    return (
      <section className="Panel">
        <h1>ReduxPage</h1>
        <Head />
        <Body />
      </section>
    );
  }
}

// 子组件Head
class Head extends React.Component {
  componentDidMount() {
  // subscribe函数是把一个新的方法增加到事件池中,通常是获取最新的新的状态信息,从而重新渲染组件
    store.subscribe(() => {
    // 虽然在这里通过setState获取的是一个空对象,但是setState会调用forceUpdate方法,forceUpdate再调用render方法重新进行渲染。
      this.setState({});
    });
  }
  render() {
    return (
      <section className="Head">
        <h2>Head</h2>
        //组件渲染时在这里通过store里面的getState方法拿到新的数据并显示出来。
        <p>counter:{store.getState().counterReducer}</p>
      </section>
    );
  }
}

// 子组件Body
class Body extends React.Component {
  render() {
    return (
      <section className="Body">
        <h2>Body</h2>
        <button onClick={this.add}>add</button>
        <button onClick={this.minus}>minus</button>
        <button onClick={this.asyAdd}>asyAdd</button>
      </section>
    );
  }

  add = () => {
  // 派发新的任务,传入新的state和action,我们不用传入state,因为数据简单,只需要操纵原来的数据,可以通过action告诉reducer怎么操纵原来的数据,并通过createStore里面的state接收reducer新返回的数据,在通过getState拿到新的数据。
    store.dispatch({ type: "add" });
  };

  minus = () => {
    store.dispatch({ type: "minus" });
  };
  asyAdd = () =>
    store.dispatch((dispatch) => {
      setTimeout(() => {
        dispatch({ type: "add" });
      }, 1000);
    });
}

通过以上动图可以看到同步任务和异步任务都可以顺利执行。 logger也显示出了日志。

子组件和父组件上面没有任何值,说明是通过redux在进行全局数据管理。

  1. 在这个项目中,我们通过getState方法拿到初始值,并在组件首次渲染时将初始值渲染在组件上;
  2. 通过dispatch派发了一个任务,dispatch方法里面传入了一个action对象,reducer根据action执行返回一个新的值,通过createStore里面的state来接收这个值;
  3. 然后执行listenAry这个数组里面保存的所有方法,而里面的方法是通过subcribe添加进来的,subcribe添加的方法主要是获取最新的组件状态信息,获取会调用组建的render方法,组件会重新渲染;
  4. 渲染后在通过getState方法拿到createStore里面的最新的state值,并将最新的state值和组件一起渲染到页面上,这就形成了一个闭环。

通过原生JS实现redux,logger和thunk

疑惑

相信你在看完以上的代码后任然会有许多疑问,为什么createStore执行后所暴露出的方法会形成一个闭环,combineReducers是怎么样实现多个子reducer的聚合的,applyMiddleware是怎么实现增强器功能的,logger是怎样实现日志输出的,thunk又是怎么样实现异步任务功能的。接下来我们通过JS来实现以上功能。

原生JS实现redux

export function createStore(reducer, enhancer) {
  //=>创建一个STORE,STATE用来存储管理的状态信息,listenAry用来存储事件池中的方法
  //=>STATE不用设置初始值,因为第一次DISPATCH执行REDUCER,STATE没有值,走的是REDUCER中赋值的默认值信息,我们自己会在创建容器的时候就把DISPATCH执行一次!
  //enhancer存在并且是函数的话就执行它。
  if (enhancer && typeof enhancer === 'function') {
    return enhancer(createStore)(reducer);
  }

  let currentState = undefined;
  let listenAry = [];

  //=>DISPATCH:基于DISPATCH实现任务派发
  function dispatch(action) {
    //1.执行REDUCER,修改容器中的状态信息(接收REDUCER的返回值,把返回的信息替换原有的STATE),值得注意的是:我们是把返回值全部替换STATE,所有要求REDUCER中在修改状态之前,要先把原始的状态信息克隆一份,在进行单个的属性修改
    currentState = reducer(currentState, action);
    for (let i = 0; i < listenAry.length; i++) {
      let item = listenAry[i];
      if (typeof item === "function") {
        item();
      } else {
        listenAry.splice(i, 1);
        i--;
      }
    }
    // listenAry.map((cl) => cl());
  }

  dispatch({ type: "?INIT_DEFAULT_STATE" });

  function getState() {
    // 我们需要保证返回的状态信息不能和容器中的STATE是同一个堆内存(否则外面获取状态信息后,直接就可以修改容器中的状态了,这不符合DISPATCH->REDUCER才能改状态的规范)
    return JSON.parse(JSON.stringify(currentState)); // 深度克隆对象
  }

  //=>SUBSCRIBE:向事件池中追加方法
  function subscribe(listener) {
    //1.向容器中追加方法(重复验证)
    let isExit = listenAry.includes(listener);
    if (!isExit) {
      listenAry.push(listener);
    }

    // 返回一个方法:执行返回的方法会把当前绑定的方法在事件池中移除掉
    return function unsubscribe() {
      let index = listenAry.indexOf(listener);
      // listenAry.splice(index, 1);//=>可能会引发数组塌陷
      listenAry[index] = null;
    };
  }

  return {
    dispatch,
    getState,
    subscribe,
  };
}

export function combineReducers(reducers) {
  // REDUCERS:传递进来的REDUCER对象集合
  return function reducer(state = {}, action) {
    // DISPATCH派发执行的时候,执行的是返回的REDUCER,这里也要返回一个最终的STATE对象替换原有的STATE,而且这个STATE中包含每个模块的状态信息{counterReducer:...}
    // 我们所谓的REDUCER合并,其实就是DISPATCH派发的时候,把每一个模块的REDUCER都单独执行一遍,把每个模块返回的状态最后汇总在一起,替换容器中的状态信息
    let newState = {};
    for (let key in reducers) {
      if (!reducers.hasOwnProperty(key)) break;
      // reducers[key]:每个模块单独的REDUCER
      // state[key]:当前模块在REDUX容器中存储的状态信息
      // 返回值是当前模块最新的状态,把它在放到NEW-STATE中
      newState[key] = reducers[key](state[key], action);
    }
    return newState;
  };
}

// 在createStore中执行applyMiddleware(...middleWares)返回applyMiddle函数,并通过middleWares在数组中保存了中间件,比如logger,thunk
export function applyMiddleware(...middleWares) {
  // 在createStore内部执行后,返回一个匿名函数,并通过createStore这个形参保存了最新的createStore函数。
  return function applyMiddle(createStore) {
    // 在createStore内部再次执行这个匿名函数,保存了reducer函数
    return (...arg) => {
      // 在内部通过createStore(...arg)执行了createStore(reducer),返回了一个对象,保存有dispatch,getState,subscribe等方法,并通过闭包保存了两个值,分别是state和listenAry
      const store = createStore(...arg);
      const midApi = {
        getState: store.getState,
        dispatch: store.dispatch,
      };
      // 通过middleWare数组的map方法返回了中间件执行后的函数
      const chain = middleWares.map((mw) => mw(midApi));
      // 通过compose方法中的reduce将两个中间件函数折叠为一个,从前向后执行参数为store.dispatch
      const dispatch = compose(...chain)(store.dispatch);
      //  将两个中间件函数折叠为一个函数再作为新的dispatch,将它作为新的对象的一部分返回给外部的store
      return {
        ...store,
        dispatch,
      };
    };
  };
}

function compose(...funcs) {
  if (funcs.length === 0) {
    return () => {
      console.log("empty function");
    };
  } else if (funcs.length === 1) {
    return funcs[0];
  } else {
    return funcs.reduce((left, right) => (...args) => right(left(...args)));
  }
}

特别注意

在原生的createStore方法里面,subscribe方法设计有缺陷会导致数组塌陷问题 以下代码:

return function unsubscribe() {
  let index = listenAry.indexOf(listener);
  listenAry.splice(index, 1);// 可能会引发数组塌陷
};

subscribe执行后会返回一个unsubscribe函数,这个函数通过记录添加在任务数组中的当前函数的位置,执行后可以删除当前添加的函数,但是它是立即删除,假设一下,如果我们添加了3个方法,其中第二个方法包含有删除第一个方法的代码,第二个方法执行后会立即在数组中删除第一个方法,这会导致数组中的元素整体前移一位,第三个方法的索引变到了第二个位置,但是数组还是会执行第三个索引上的函数,会漏过原先的函数,我们因该避免这件事情发生。

解决方案:

return function unsubscribe() {
  let index = listenAry.indexOf(listener);
  // listenAry.splice(index, 1);// 可能会引发数组塌陷
  // 先将要删除的方法置为空,再在便利的时候,删除值为空的那一项,再将索引减一
  listenAry[index] = null;
};

// 在dispatch中遍历的时候,删除值为空的那一项,再将索引减一
for (let i = 0; i < listenAry.length; i++) {
  let item = listenAry[i];
  if (typeof item === "function") {
    item();
  } else {
    listenAry.splice(i, 1);
    i--;
  }
}

下面再来实现logger和thunk函数

//在logger参数里面将midApi解构出来,拿到其中的getState方法
export function logger({ getState }) {
  // 将store.dispatch或者其他的函数传入进去,但是其他函数也会包含store.dispatch函数
  // 将最里层的箭头函数返回。
  return (dispatch) => (action = {}) => {
    let oldValue = getState();
    dispatch(action);
    let newValue = getState();

    console.log(
      action.type + "执行了" + "执行时间是 " + new Date().toLocaleTimeString()
    );
    console.log("旧值:", oldValue);
    console.log("action: ", action);
    console.log("新值:", newValue);
    console.log("-------------分割线-------------");
  };
}

// 在applyMiddleware里面执行这一函数,返回里面的箭头函数
export function thunk() {
  // 将store.dispatch或者其他的函数传入进去,但是其他函数也会包含store.dispatch函数
  // 将最里层的箭头函数返回。
  return (dispatch) => (action) => {
    if (typeof action === "function") {
      return action(dispatch);
    }
    dispatch(action);
  };
}

现在将项目中的redux,logger和thunk替换为我们自己写的再来试一下,看是否能用

import React from "react";
// 我们自己的redux在KRedux文件里面,logger和thunk在MyReduxStore文件里面,看一下效果。
import { createStore, combineReducers, applyMiddleware } from "../../KRedux";
import { logger, thunk } from "../../store/MyReduxStore";

function counterReducer(state = 0, action) {
  switch (action.type) {
    case "add":
      return state + 1;
    case "minus":
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(
  combineReducers({ counterReducer }),
  applyMiddleware(thunk, logger)
);

// 3个class组件我就不写了,和上面一模一样。

动态图,和上面的没有任何区别,怀疑代码是否有效的读者可以copy我的代码,自己尝试一下,在我的电脑上是没有问题的: 我自己写的logger记录器:

不能捕获异步信息,实在能力有限,望读者见谅 组件内容信息:

父组件和子组件上没有任何props属性和contenxt信息,说明全部是基于redux进行数据管控。

手写react-redux源码

接下来我们自己实现react-redux源码,它可以帮我们自动发布获取最新状态信息的任务,更简便的实现redux中的发布订阅。
react-redux中重要的方法有两个:provider和connect

  1. Provider 为后代组件提供store;
  2. connect 为组件提供数据和变更方法。

react-redux代码:

import React, { useState, useContext, useEffect } from "react";

const Context = React.createContext();

// PROVIDER:当前项目的“根”组件
//  1. 接收通过属性传递进来的STORE,把STORE挂载到上下文中,这样当前项目中任何一个组件中,想要使用REDUX中的STORE,直接通过上下文获取即可
//  2. 在组件的RENDER中,把传递给PROVIDER的子元素渲染
export const Provider = (props) => {
  return (
    // 使用Context.Provider把store放到上下文中,并渲染子元素
    <Context.Provider value={props.store}>{props.children}</Context.Provider>
  );
};

//CONNECT:高阶组件,返回函数组件,也可以用class组将实现
export const connect = function (
  // mapStateToProps:回调函数,把REDUX中的部分状态信息挂载到指定组件的属性上
  // 把RETURN的对象挂载到属性上
  mapStateToProps = (state) => state,
  // 对象或者回调函数,把一些需要派发的任务方法也挂载到组件的属性上,返回的方法中有执行dispatch派发任务的操作
  mapDispatchToProps
) {
  // connectMid
  // 参数:传递进来的是要操作的组件,我们需要把指定的属性和方法都挂载到当前组件的属性上
  // 返回值
  // 1.返回一个新的组件Proxy(代理组件),在代理组件中,我们要获取Provider在上下文中存储的store。
  // 2.紧接着获取store中的state和dispatch,把mapStateToProps回调函数执行、mapDispatchToProps对象值拿到。
  // 3.接收返回的结果,把这些结果挂载到Component这个要操作组件的属性上
  // 4.把传递进来的Cmp组件渲染到页面上
  return function connectMid(Cmp) {
    return function Proxy() {
      const store = useContext(Context);
 
      function getProps() {
        // 在当前示例中,stateProps值为{counter: {counterReducer: 0}}
        const stateProps = mapStateToProps(store.getState());
        // bindActionCreators方法很重要,它将我们传递进来的方法和store.dispatch绑定在一起
        const dispatchProps =
          mapDispatchToProps instanceof Object
            ? bindActionCreators(mapDispatchToProps, store.dispatch)
            : mapDispatchToProps instanceof Function
            ? mapDispatchToProps(store.dispatch)
            : null;
        // 返回一个新对象,包含stateProps值和dispatchProps对象
        return {
          ...stateProps,
          ...dispatchProps,
        };
      }

      // 在组件渲染完后通过store的subscribe方法发布一个setProps方法,可以获取最新的store信息,并再次触发渲染
      useEffect(() => {
        store.subscribe(() => {
          setProps({ ...getProps() });
        });
      });

      // useState设置调用getProps()初始化state值,并设置setProps获取最新的store信息
      const [props, setProps] = useState({ ...getProps() });
      return <Cmp {...props} />;
    };
  };
};

// 传递进来两个值分别为传递进来的方法对象和store.dispatch方法
function bindActionCreators(creators, dispatch) {
  // Object.keys(creators)获取方法对象键值组成的数组,
  // 通过这个数组的reduce方法生成一个对象ret,对象键为:数组里面的值,值为store.dispatch(creators[item](...args)),
  return Object.keys(creators).reduce((ret, item) => {
    ret[item] = bindActionCreator(creators[item], dispatch);
    // 将对象返回,通过dispatchProps接收
    return ret;
  }, {});
}

// 生成一个新方法,内容为store.dispatch(creators[item]())。
// 就是store.dispatch方法参数是creators[item](...args)
function bindActionCreator(creator, dispatch) {
  return (...args) => dispatch(creator(...args));
}

代码实例:
store文件,redux还是有点小问题,单独使用,实现异步调用,配合react-redux使用,异步会出现死递归的情况,有能力的人可以改一下我的代码

import thunk from "redux-thunk";
import logger from "redux-logger";
import { createStore, combineReducers, applyMiddleware } from "../KRedux";
// import { createStore, combineReducers, applyMiddleware } from "redux";

function counterReducer(state = 0, action) {
  switch (action.type) {
    case "add":
      return state + 1;
    case "minus":
      return state - 1;
    default:
      return state;
  }
}
const store = createStore(
  combineReducers({ counterReducer }),
  applyMiddleware(logger, thunk)
);

export default store;

index文件:

import { Provider } from "./KReact-redux";
import store from "./store/MyReactReduxStore";
import {
  HeadPage,
  BodyPage,
} from "./reduxPage/reduxDemo/MyReactReduxPage";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.Fragment>
    <Provider store={store}>
      <div className="Panel">
        <h1>MyReactReduxPage</h1>
        <HeadPage />
        <BodyPage />
      </div>
    </Provider>
  </React.Fragment>,
  rootElement
);

MyReactReduxPage文件:

import React from "react";
import { connect } from "../../KReact-redux";

class Head extends React.Component {
  render() {
    const { counter } = this.props;
    return (
      <section className="Head">
        <h1>HeadPage</h1>
        <p>counter:{counter.counterReducer}</p>
      </section>
    );
  }
}
export const HeadPage = connect((state) => {
  return { counter: state };
}, null)(Head);

// 子组件Body
class Body extends React.Component {
  render() {
    const { add, minus, asyAdd } = this.props;
    return (
      <section className="Body">
        <h2>Body</h2>
        <button onClick={add}>add</button>
        <button onClick={minus}>minus</button>
        <button onClick={asyAdd}>点击</button>
      </section>
    );
  }
}
export const BodyPage = connect(null, {
  add: () => {
    return { type: "add" };
  },
  minus: () => {
    return { type: "minus" };
  },
  asyAdd: () => {
    return (dispatch) => {
      setTimeout(() => {
        dispatch({ type: "add" });
      }, 1000);
    };
  },
})(Body);

整体组件结构:
Provider组件 Proxy代理组件:

两个子组件:

后记

到这里,redux及相关的源码整体复现完毕,虽然还是有一点小问题,但是帮助我们理解整个redux流程还是很有帮助的,虽然现在新项目中大都使用hooks,但是redux还是有很重要的作用的,了解redux对于我们的工作,有很积极的意义。