Redux手动联系react

136 阅读5分钟

什么是Redux

Redux是JavaScript的一个状态容器,用来帮助JS进行状态的管理和追踪。

Redux的核心理念

store action reducer

store

通过创建一个容器来规范各个组件对容器内状态的调用,让数据变的可追踪。

action

  • Redux要求必须使用action来更新数据
    • 所有的数据的变化,都必须通过派发action来更新
    • action是一个普通的JS对象,用来描述这次更新的type和content

reducer

  • reducer负责将state和action联系起来
    • reducer是一个纯函数
    • reducer负责将传入的state和action结合起来形成一个新的state

Redux三大原则

  • 单一数据源
  • state是只读的
    • 确保修改state的方法一定是触发action,不能试图从其他地方用任何方式来修改state
    • 确保了view或网络请求都不能直接修改state,只能通过action来描述自己想要如何修改state
    • 这样可以保证所有的修改都集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竞态)的问题
  • 使用纯函数来执行修改
    • 通过reducer将旧state好actions联系在一起,并且返回一个新的state
    • 随着应用程序复杂度的增加,我们可以将reducer拆分成多个小的reducers,分别操作不同的state tree的一部分
    • 所有的reducer都应该是纯函数,不能产生任何的副作用。

单独使用redux

image.png

新建一个store文件

  • constance.js存放抽离出来的type类型常量

export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
export const SUB_NUMBER = "SUB_NUMBER";
  • reducer.js存放纯函数reducer

import { INCREMENT, DECREMENT, SUB_NUMBER } from "./constance";

const initialState = { counter: 10 };

export const reducer = (state = initialState, { type, number }) => {
  switch (type) {
    case INCREMENT:
      return { ...state, counter: state.counter + 1 };
    case DECREMENT:
      return { ...state, counter: state.counter - 1 };
    case SUB_NUMBER:
      return { ...state, counter: state.counter - number };
    default:
      return state;
  }
};
  • actionCreaters.js存放action代码
import { DECREMENT, INCREMENT, SUB_NUMBER } from "./constance";

export const incrementAction = {
  type: INCREMENT,
};

export const decrementAction = {
  type: DECREMENT,
};

export const subNumberAction = (number) => ({
  type: SUB_NUMBER,
  number,
});

  • index.js整合redux文件导出redux
import { createStore } from "redux";

import { reducer } from "./reducer";

export const store = createStore(reducer);

image.png

  • 然后在页面中订阅redux,并在销毁前取消订阅,然后再派发actions
import React, { PureComponent } from "react";
import { store } from "../store";
import { decrementAction, subNumberAction } from "../store/actionCreaters";
export default class home extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0,
    };
  }
  //订阅redux
  componentDidMount() {
    this.sub = store.subscribe(() => {
      this.setState({
        counter: store.getState().counter,
      });
    });
  }

  //取消订阅redux
  componentWillUnmount() {
    this.sub();
  }

  render() {
    return (
      <div>
        <h1>home</h1>
        <h2>当前计数:{this.state.counter}</h2>
        <button onClick={(e) => this.decrement()}>-1</button>
        <button onClick={(e) => this.subNmuber()}>-5</button>
      </div>
    );
  }
  //派发acitons
  decrement() {
    store.dispatch(decrementAction);
  }
  subNmuber() {
    store.dispatch(subNumberAction(5));
  }
}

redux融入react

单独的使用redux,在react中,每个组件都要重复的调用派发action,订阅,取消订阅redux,过于臃肿。

我们可以自定义一个connect函数,来将组件和redux联系起来,减少重复的代码。

首先我们可以定义个connect高阶函数,将需要使用redux的组件传入,并在该函数中引入redux,返回一个新的组件,并将需要的数据添加到props中

相对于组件来说,如果有人提供它和redux的联系,那么它只要告诉这个人,它需要从redux中获得什么,并给他什么。所以connect函数的三个参数可以确定下来。要什么,给什么,哪个组件要

image.png

这样需要的数据都能从props中获得,那么组件的代码可以删减成如下

image.png

接下来我们需要具体定义mapStateToProps(要什么)、mapDispatchToProps(给什么)。当前组件需要redux中state的counter数据,需要给redux派发两个不同的actions。

image.png

有了上述的需求,那么我们这个connect函数可以具体设计出来。首先他需要接收一个需要和redux联系的组件,然后收下这个组件的要求:要redux什么、给redux什么,然后返回一个新的和redux联系的组件。

image.png

然后从redux中拿出需要的状态,并派发相应的actions,都传递给返回的新组件

image.png

最后需要将这些数据通过组件state联系起来,让react去处理页面ui的变化。

image.png

手动联系redux和react的代码

目录结构

image.png

connect函数部分

import React, { PureComponent } from "react";
import { store } from "../store";

export function connect(mapStateToProps, mapDispatchToProps) {
  return function enhanceHOC(WrappedComponent) {
    return class extends PureComponent {
      constructor(props) {
        super(props);
        this.state = {
          mapState: store.getState(),
        };
      }
      //订阅redux
      componentDidMount() {
        this.sub = store.subscribe(() => {
          this.setState({
            mapState: store.getState(),
          });
        });
      }
      //取消订阅redux
      componentWillUnmount() {
        this.sub();
      }
      render() {
        return (
          <WrappedComponent
            {...this.props}
            {...mapStateToProps(store.getState())}
            {...mapDispatchToProps(store.dispatch)}
          ></WrappedComponent>
        );
      }
    };
  };
}

Home组件部分

import React, { PureComponent } from "react";
import { decrementAction, subNumberAction } from "../store/actionCreaters";
import { connect } from "../utils/connect";

class Home extends PureComponent {
  render() {
    const { decrement, subNmuber, counter } = this.props;
    return (
      <div>
        <h1>home</h1>
        <h2>当前计数:{counter}</h2>
        <button onClick={(e) => decrement()}>-1</button>
        <button onClick={(e) => subNmuber(5)}>-5</button>
      </div>
    );
  }
}

const mapStateToProps = (state) => ({
  counter: state.counter,
});

const mapDispatchToProps = (dispatch) => ({
  decrement: () => {
    dispatch(decrementAction);
  },
  subNmuber: (num) => {
    dispatch(subNumberAction(num));
  },
});

export default connect(mapStateToProps, mapDispatchToProps)(Home);

彻底抽离connect函数方法

上面虽然实现了手动联系redux和recat的方法,但是这个connect函数,仍然依赖我自己项目中的某个路径下导入的store,不能很好的去复用,所以我们对该函数进行一个优化,让用户自己传入需要使用的store。

那么我们只要定义一个新的context方法,让它去给connect函数传递需要的store

image.png

然后我们在index.js全局入口处,将store通过context的方式给子孙组件通信传递数据。

image.png

接着既然后续组件能收到根组件传递过来的redux中的store,那么我们可以在connect函数中,获取到connect对象,并从中取出,根组件传递过来的对象。

image.png

修改后的connect函数部分代码

import React, { PureComponent } from "react";

import { StoreContext } from "./context";

export function connect(mapStateToProps, mapDispachToProp) {
  return function enhanceHOC(WrappedComponent) {
    class EnhanceComponent extends PureComponent {
      constructor(props, context) {
        super(props, context);

        this.state = {
          storeState: mapStateToProps(context.getState()),
        };
      }

      componentDidMount() {
        this.unsubscribe = this.context.subscribe(() => {
          this.setState({
            storeState: mapStateToProps(this.context.getState()),
          });
        });
      }

      componentWillUnmount() {
        this.unsubscribe();
      }

      render() {
        return (
          <WrappedComponent
            {...this.props}
            {...mapStateToProps(this.context.getState())}
            {...mapDispachToProp(this.context.dispatch)}
          />
        );
      }
    }

    EnhanceComponent.contextType = StoreContext;

    return EnhanceComponent;
  };
}