React 学习(十一) Redux

Redux

  • Single Data Source
    • All states is stored in an Object Tree, and the Object Tree is stored in only one store
    • Easy to maintain, track and modify
  • State is read-only
    • The only way to change states is to use action
  • Use pure function

Redux Process

sequenceDiagram participant v as view participant r as reducer participant s as state v->>r: dispatch(action) r->>s: change state s->>s: Shallow comparison and Update s->>v: call render() to update view
  • Basic Usage

    // node index.js
    const redux = require("redux");
    
    // store
    const store = redux.createStore(reducer);
    
    // state
    const initState = {
      counter: 0,
    };
    
    // subscribe
    store.subscribe(() => {
      console.log("counter", store.getState().counter);
    });
    
    // reducer
    const reducer = (state = initState, action) => {
      const { type, payload } = action;
      switch (type) {
        case "INCREMENT":
          return { ...state, counter: state.counter + 1 };
        case "DECREMENT":
          return { ...state, counter: state.counter + 1 };
        case "ADD_NUMBER":
          return { ...state, counter: state.counter + payload.counter };
        case "SUB_NUMBER":
          return { ...state, counter: state.counter - payload.counter };
    
        default:
          return state;
      }
    };
    
    // action
    const action1 = () => ({
      type: "INCREMENT",
    });
    
    const action2 = () => ({
      type: "DECREMENT",
    });
    
    const action3 = (num) => ({
      type: "ADD_NUMBER",
      payload: {
        num,
      },
    });
    
    const action4 = (num) => ({
      type: "SUB_NUMBER",
      payload: {
        num,
      },
    });
    
    // dispatch
    store.dispatch(action1());
    store.dispatch(action1());
    store.dispatch(action2());
    store.dispatch(action2());
    store.dispatch(action3(5));
    store.dispatch(action4(12));
    
  • Module Redux

    /**
     * │  index.js
     * │  package-lock.json
     * │  package.json
     * └─store
     *         actionCreator.js
     *         constants.js
     *         index.js
     *         reducer.js
     */
    
    // store
    // index.js
    import redux from "redux";
    import reducer from "./reducer.js";
    const store = redux.createStore(reducer);
    export default store;
    
    // actionCreator.js
    import { ADD_NUMBER, SUB_NUMBER } from "./constants.js";
    
    export const addAction = (num) => ({
      type: ADD_NUMBER,
      num,
    });
    
    export const subAction = (num) => ({
      type: SUB_NUMBER,
      num,
    });
    
    // reducer.js
    import { ADD_NUMBER, SUB_NUMBER } from "./constants.js";
    const defaultState = {
      counter: 0,
    };
    
    function reducer(state = defaultState, action) {
      switch (action.type) {
        case ADD_NUMBER:
          return { ...state, counter: state.counter + action.num };
        case SUB_NUMBER:
          return { ...state, counter: state.counter - action.num };
        default:
          return state;
      }
    }
    
    export default reducer;
    
    // index.js
    import store from "./store/index.js";
    import { addAction, subAction } from "./store/actionCreator.js";
    
    store.subscribe(() => {
      console.log(store.getState());
    });
    
    store.dispatch(addAction(10));
    store.dispatch(addAction(15));
    store.dispatch(addAction(10));
    store.dispatch(subAction(20));
    store.dispatch(subAction(3));
    store.dispatch(subAction(1));
    

Connect(HOC)

// App.js
const mapStateToProps = (state) => ({
  xxx: xxx
});

const mapDispatchToProps = (dispatch) => ({
  func() {
    dispatch(action())
  }
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
// Basic connect.js
import React, { PureComponent } from "react";
import store from "../store";

export const connect = (mapStateToProps, mapDispatchToProps) => {
  return (WrappedComponent) => {
    return class extends PureComponent {
      constructor(props) {
        super();

        this.stateStore = {
          // Listen the incoming state change through own state, and call render function to update
          stateStore: mapStateToProps(store.getState()),
        };
      }

      componentDidMount() {
        this.unSubscribe = store.subscribe(() => {
          this.setState({
            stateStore: mapStateToProps(store.getState()),
          });
        });
      }

      componentWillUnmount() {
        this.unSubscribe();
      }

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

props and context[^props]

// Advanced(Use Context) connect.js
// context.js
import React from "react";
const StoreContext = React.createContext();
export { StoreContext };

// connect.js
import React, { PureComponent } from "react";
import { StoreContext } from "./context";

export const connect = (mapStateToProps, mapDispatchToProps) => {
  return (WrappedComponent) => {
    class EnhanceComponent extends PureComponent {
      constructor(props, context) {
        super();

        this.stateStore = {
          // This.context hasn't been assigned here yet
          stateStore: mapStateToProps(context.getState()),
        };
      }

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

      componentWillUnmount() {
        this.unSubscribe();
      }

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

    EnhanceComponent.contextType = StoreContext;

    return EnhanceComponent;
  };
};

// index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import store from "./store";
import { StoreContext } from "./utils/context";

ReactDOM.render(
  <StoreContext.Provider value={store}>
    <App />
  </StoreContext.Provider>,
  document.getElementById("root")
);

Middleware

  • redux-thunk

    dispatch(action Object ==> action Function): We can make network requests in the funciton

    // App.js
    import { getHomeMultidataAction } from "../store/home/actionCreator";
    const mapDispatchToProps = (dispatch) => ({
      // What we pass in here is a function, not an obejct
      // and we do not need to call the function manually
      getHomeMultidata() {
        dispatch(getHomeMultidataAction);
      },
    });
    
    // actionCreator.js
    import axios from "axios";
    import { CHANGE_BANNER, CHANGE_RECOMMEND } from "./constants.js";
    export const changeBannerAction = (banner) => ({
      type: CHANGE_BANNER,
      banner,
    });
    export const getHomeMultidataAction = (dispatch, getState) => {
      axios({
        url: "http://123.207.32.32:8000/home/multidata",
      }).then((res) => {
        const { data } = res.data;
        // console.log(data);
        dispatch(changeBannerAction(data.banner.list));
      });
      // console.log(getState());
    };
    
    // reducer.js
    import { CHANGE_BANNER, CHANGE_RECOMMEND } from "./constants.js";
    // home
    const initalHomeState = {
      banner: [],
      recommend: [],
    };
    function homeReducer(homeInfo = initalHomeState, action) {
      switch (action.type) {
        case CHANGE_BANNER:
          return { ...homeInfo, banner: action.banner };
        case CHANGE_RECOMMEND:
          return { ...homeInfo, recommend: action.recommend };
        default:
          return homeInfo;
      }
    }
    export default homeReducer;
    
  • redux-saga

    // index.js
    import { createStore, applyMiddleware } from "redux";
    import reducer from "./reducer";
    // thunk
    import thunkMiddleware from "redux-thunk";
    // saga
    import createSagaMiddleware from "redux-saga";
    import mySaga from "./home/saga";
    const sagaMiddleware = createSagaMiddleware();
    const storeEnhancer = applyMiddleware(thunkMiddleware, sagaMiddleware);
    const store = createStore(reducer, storeEnhancer);
    // generator Function
    sagaMiddleware.run(mySaga);
    export default store;
    
    // saga.js
    import axios from "axios";
    import { all, put, takeEvery } from "redux-saga/effects";
    import { FETCH_GET_MULTIDATA } from "./constants";
    import { changeBannerAction, changeRecommendAction } from "./actionCreator";
    
    function* fetchGetMultidata(action) {
      const {
        data: { data },
      } = yield axios({
        url: "http://123.207.32.32:8000/home/multidata",
      });
      yield all([
        put(changeBannerAction(data.banner.list)),
        put(changeRecommendAction(data.recommend.list)),
      ]);
    }
    function* mySaga() {
      // takeLaste: Excute the latest one
      // takeEvery: Every will be excuted
      // action.type, generator
      yield takeEvery(FETCH_GET_MULTIDATA, fetchGetMultidata);
    }
    export default mySaga;
    
    // actionCreator.js
    export const fetchGetMultidataAction = () => ({
      type: FETCH_GET_MULTIDATA,
    });
    
    // App.js
    const mapDispatchToProps = (dispatch) => ({
      // Pass in an object
      fetchGetMultidata() {
        dispatch(fetchGetMultidataAction());
      },
    });
    
每天进步一点点
原文地址:https://www.cnblogs.com/smallstars/p/14074902.html