Redux源码学习

55 阅读5分钟

思维导图

image.png

demo1

import React, { Component } from "react";
import store from "../store";
export default class ReduxPage extends Component {
  componentDidMount() {
    // 告诉redux,一旦state变化(一旦执行dispatch函数),就执行的事件
    this.unsubscribe = store.subscribe(() => {
      this.forceUpdate();
    });
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  add = () => {
    store.dispatch({ type: "ADD" });
  };

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

    store.dispatch((dispatch, getState) => {
      setTimeout(() => {
        dispatch({ type: "MINUS" });
      }, 1000);
    });
  };

  promiseMinus = () => {
    store.dispatch(
      Promise.resolve({
        type: "MINUS",
        payload: 100,
      })
    );
  };

  render() {
    return (
      <div>
        <h3>ReduxPage</h3>
        <p>{store.getState().count}</p>
        <button onClick={this.add}>add</button>
        <button onClick={this.minus}>minus</button>
        <button onClick={this.promiseMinus}>promiseMinus</button>
      </div>
    );
  }
}

入口

import createStore from "./createStore";
import applyMiddleware from "./applyMiddleware";
import compose from "./compose";
import combineReducers from "./combineReducers";
import bindActionCreators from "./bindActionCreators";

export {
  createStore,
  applyMiddleware,
  compose,
  combineReducers,
  bindActionCreators,
};

createStore

export default function createStore(reducer, enhancer) {
  if (enhancer) {
    return enhancer(createStore)(reducer);
  }
  let currentState;
  let currentListeners = [];

  function getState() {
    return currentState;
  }

  function dispatch(action) {
    currentState = reducer(currentState, action);
    currentListeners.forEach((listener) => listener());
  }

  function subscribe(listener) {
    currentListeners.push(listener);

    return () => {
      const index = currentListeners.indexOf(listener);
      currentListeners.splice(index, 1);
    };
  }

  dispatch({ type: "ASASASASASA/REDUX" });

  return {
    getState,
    dispatch,
    subscribe,
  };
}

  • createStore里管理着currentState和currentListners这两个状态
  • reucer在dispath里面进行调用,改变状态后将所有订阅的组件都强制刷新一下。这里全都刷新太浪费性能了,可以做优化
  • 订阅之后记得卸载组件的时候要取消订阅
  • 在createStore里手动执行一次dispatch,让currentState获取到初始状态
  • enhancer是中间件,主要用来对createStore里的dispatch功能进行加强。简单版本的dispatch只能接受对象为参数,进行加强后可以接收函数作为参数,并且能处理异步

compose

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return (arg) => arg;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce(
    (a, b) =>
      (...args) =>
        a(b(...args))
  );
}

compose是一个utils函数,主要作用是对多个函数进行聚合,实现f1(f2(f3(...ars)))的函数调用功能

applyMiddle

import compose from "./compose";

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer) => {
    const store = createStore(reducer);
    let dispatch = store.dispatch;

    // todo 加强dispatch

    const midAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args),
    };

    const middlewareChain = middlewares.map((middleware) => middleware(midAPI));
    // console.log(middlewareChain, "middleware chain");
    // debugger;

    // 加强版的dispatch
    // 把所有的中间件的函数都执行了,同时还执行store.dispatch
    dispatch = compose(...middlewareChain)(store.dispatch);
    // console.log(dispatch, "dispatch middleware chain");
    // debugger;

    return {
      ...store,
      // 加强版的dispatch
      dispatch,
    };
  };
}

  • 这是实现中间件原理的核心代码,虽然寥寥几行代码,但是功能强大。其主要作用就是通过中间件,加强dispatch功能,creteStore里的其它参数照抄
  • 在midApi中,dispatch不能直接用store的dispatch,写成箭头函数形式的目的是为了能够在上下文中找到对应的dispatch
  • middlewares.map的时候,所有中间件已经执行一次了,全都返回next=>action=>{...}的形式
  • compose(...middlewareChain)(store.dispatch)返回的只有一个action=>{...}了,每一次action里的next都是下一个中间件返回的函数结果,这个时候dispatch=action=>{...}调用的时候就是一个洋葱模型的形式了

store

// import { createStore, applyMiddleware, combineReducers } from "redux";
import { createStore, applyMiddleware, combineReducers } from "../redux-nut";

// import thunk from "redux-thunk";
// import logger from "redux-logger";
import promise from "redux-promise";

// 定义了store修改规则
export function countReducer(state = 0, action) {
  switch (action.type) {
    case "ADD":
      return state + 1;
    case "MINUS":
      return state - 1;
    default:
      return state;
  }
}

// 创建store
const store = createStore(
  // countReducer,
  combineReducers({
    count: countReducer,
  }),
  applyMiddleware(
    // promise,
    logger,
    thunk
  )
);

export default store;

function logger({ getState, dispatch }) {
  return (next) => (action) => {
    // debugger;
    console.log("-----------------------"); //sy-log

    console.log(action.type + "执行了"); //sy-log

    const prevState = getState();

    console.log("prev state", prevState); //sy-log

    const returnValue = next(action);

    // 等状态值修改之后,再执行getState,拿到了新的状态值
    const nextState = getState();

    console.log("next state", nextState); //sy-log

    console.log("-----------------------"); //sy-log

    return returnValue;
  };
}

function thunk({ getState, dispatch }) {
  return (next) => (action) => {
    // debugger;
    if (typeof action === "function") {
      return action(dispatch, getState);
    }
    return next(action);
  };
}

这里重点关注thunk和logger这两个中间件的实现,使用了高阶函数和函数柯里化的用法,与applyMiddle结合起来便能理解中间件的精髓。

combineRudecers

export default function combineReducers(reducers) {
  // 返回一个总的reducer (prevState, action)=>nextState
  return function combination(state = {}, action) {
    let nextState = {};
    let hasChanged = false;

    for (const key in reducers) {
      const reducer = reducers[key];
      nextState[key] = reducer(state[key], action);
      hasChanged = hasChanged || nextState[key] !== state[key];
    }

    // {a:1, b:1} {a:1}
    hasChanged =
      hasChanged || Object.keys(nextState).length !== Object.keys(state).length;

    return hasChanged ? nextState : state;
  };
}

主要实现思路是返回一个函数reducer,然后在这个函数reducer里对每个reducer进行遍历修改每一个reducer对应的state状态,在这里hasChange那里的逻辑作用不大。需要注意的是这里的实现不是把每个reducer里的switch进行合并。

demo2

import { Component } from "react";
// import { connect } from "react-redux";
import { connect } from "../react-redux-nut/";
// import { bindActionCreators } from "redux";
import { bindActionCreators } from "../redux-nut";

// HOC higer order Component,高阶组件:是个函数,接受组件作为参数,返回新的组件
export default connect(
  // mapStateToProps,
  //   (state, ownProps) => {
  //     console.log("ownProps", ownProps); //sy-log

  //     return state;
  //   }
  ({ count }) => ({ count }),
  // mapDispatchToProps function|object

  //   (dispatch) => {
  //     let creators = {
  //       add: () => ({ type: "ADD" }),
  //       minus: () => ({ type: "MINUS" }),
  //     };

  //     creators = bindActionCreators(creators, dispatch);

  //     return { dispatch, ...creators };
  //   }
  {
    add: () => ({ type: "ADD" }),
    minus: () => ({ type: "MINUS" }),
  }
)(
  class ReactReduxPage extends Component {
    render() {
      console.log("props", this.props); //sy-log
      const { count, dispatch, add, minus } = this.props;
      return (
        <div>
          <h3>ReactReduxPage</h3>
          <button
            onClick={() => {
              dispatch({ type: "ADD" });
            }}
          >
            dispatch:{count}
          </button>
          <button onClick={add}>add: {count}</button>
        </div>
      );
    }
  }
);

react-redux

// Context传值 跨组件层级传递数据

import React, {
  useCallback,
  useContext,
  useEffect,
  useLayoutEffect,
  useReducer,
  useState,
  useSyncExternalStore,
} from "react";
import { bindActionCreators } from "../redux-nut";

// ! 1. 创建context对象
const Context = React.createContext();

// ! 2. Provider组件传递value (store)
export function Provider({ store, children }) {
  return <Context.Provider value={store}>{children}</Context.Provider>;
}

// ! 3.后代消费Provider传递下来的value
// * contextType 只能用在类组件,只能订阅单一的context来源
// * useContext 只能用在类组件或者自定义Hook中
// * Consumer 没有组件限制,注意使用方式

//返回一个新的函数式组件(props) => {...}
export const connect =
  (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => (props) => {
    const store = useContext(Context);
    const { getState, dispatch, subscribe } = store;

    // const stateProps = mapStateToProps(getState());
    let dispatchProps = { dispatch };

    if (typeof mapDispatchToProps === "function") {
      dispatchProps = mapDispatchToProps(dispatch);
    } else if (typeof mapDispatchToProps === "object") {
      dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
    }

    // const [, forceUpdate] = useReducer((x) => x + 1, 0);

    const forceUpdate = useForceUpdate();

    // // DOMeffect
    // useLayoutEffect(() => {
    //   const unsubscribe = subscribe(() => {
    //     forceUpdate();
    //   });
    //   return () => {
    //     unsubscribe();
    //   };
    // }, [subscribe]);

    const state = useSyncExternalStore(() => {
      subscribe(forceUpdate);
    }, getState);

    // console.log("checked", state === getState()); //sy-log

    const stateProps = mapStateToProps(state);

    return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />;
  };

function useForceUpdate() {
  const [state, setState] = useState(0);
  const update = useCallback(() => {
    setState((prev) => prev + 1);
  }, []);

  return update;
}

export function useSelector(selector) {
  const store = useContext(Context);

  const { getState, subscribe } = store;

  // const selectedState = selector(getState());

  const forceUpdate = useForceUpdate();

  // // DOMeffect
  // useLayoutEffect(() => {
  //   const unsubscribe = subscribe(() => {
  //     forceUpdate();
  //   });
  //   return () => {
  //     unsubscribe();
  //   };
  // }, [subscribe]);

  const state = useSyncExternalStore(() => {
    subscribe(forceUpdate);
  }, getState);

  const selectedState = selector(state);

  return selectedState;
}

export function useDispatch() {
  const store = useContext(Context);

  const { dispatch } = store;

  return dispatch;
}

  • 从上面redux使用的案例可以看到,每次使用都得自己订阅强刷新,这样页面多的时候就会显得很冗余,react-redux就是把这些共用的订阅过程进行一个合并,由react-reux来完成这些操作,而页面只需要使用state及dispatch即可。
  • 对于类组件,核心实现是connect函数,这是一个高阶组件,传入一个组件,返回一个包装后的新组件,在包装的过程中通过context获取到父组件传下来的store,通过使用getState、dispatch、subscribe这三个api来对状态进行管理和页面刷新
  • 函数时组件的核心实现是useSelector和useDispatch这两个api,主要也是通过context获取到父组件传下来的store,然后通过getState、dispatch、subscribe这三个api来对状态进行管理和页面刷新