[Redux 源码] 来聊聊 Redux

4,144 阅读5分钟

[Redux 源码] 来聊聊 Redux

如同世界上的很多事情一样,Redux 也从昔日的 React 的大网红,逐渐退出前端的舞台。这很正常。Redux 也算完成了它的使命,作为前端开发人员还是要充满感激,准备在未来的某一天挥手告别 Redux

2022 年应不应该学习 Redux React Redux

这个问题和以下问题一样无聊,哈哈哈!

2022 年前端还有没有前途? 2021 年前端还有没有前途? 2020 年前端还有没有前途? 这个问题,笔者在知乎发现, 从2015年开始,就有人在问。

笔者的答案是肯定,可以说掌握了 Redux, 其他的一些状态管理库,比如( zustand,Recoil,Unstated Next…… ) 再去看源码就非常好理解了。

为什么一开始是 Redux 而不是 Context 优势 和 区别 在哪?

Context 不支持局部订阅,这也是被大家诟病的重要原因。这也就意味着,只要引用类型中的单个属性/成员 发生变化,其余没有发生变化的成员也要全部 rerender。这里其实有解决方案就是下面代码所表示的 HOC 高阶组件,将 useContext 从组件当中解耦出来,然后由高阶组件去分发简单值状态给子组件传递 props。这样子组件可以通过 React.memo 优化 props / useMemo 包裹的一些个组件。但是终究不是办法。

其他被大家诟病的原因还有,Context 状态管理太分散,Context 嵌套,没有异步相关业务的处理方案。 当然,这些都是针对有全局状态管理需求的,像一些中后台场景下,绝大多数页面的数据流转都是在当前页完成,在页面挂载的时候请求后端接口获取并消费,这种场景下并不需要复杂的数据流方案。

const Hoc =
  (Component) =>
  () => {
    const { userLists, flipLight } = useContext(Context);
    // 组件局部订阅  userLists[index]
    return (
      <Component  user={userLists[index]} />
    );
  };
  
const HocMemo = Hoc
  React.memo(({ user }) => {
    console.log('render', index);
    return (
      <div></div>
    );
  })
);

Redux 可以轻松实现支持局部订阅,而且不用我们去想怎么优化,为什么这么说? 因为只要开发者有意识的,正确的使用 useSelector 我们就可以实现UI和数据之间的局部订阅

当然,Redux 还支持各种中间件的级联,和异步业务的处理。

function ReduxCpn({ index }) {
  const isLit = useSelector((state) => state.userList[index]); //局部订阅
  const dispatch = useDispatch();

  console.log('render room', index);

  return (
    <div>
      <button onClick={() => dispatch(flipLight(index))}>Flip</button>
    </div>
  );
}

为什么后来又不是 Redux

第一:Hook 时代来了

第二:React Query / SWR / useRequest 异步状态管理工具出现了

第三:开发者逐渐意识到模板代码太重,逻辑大量冗余,大项目不好维护。尤其是针对于网络请求,这 一块的解决方案很难用。

Redux 源码

简单使用

class Counter1 extends React.Component {
  state = { number: store.getState().number };
  componentDidMount() {
    /* 返回取消监听函数 */ 
    this.unsubscribe = store.subscribe(() => {
      this.setState({ number: store.getStore().number });
    });
  }
  componentWillUnmount() {
    this.unsubscribe();
  }
  render() {
    return (
      <div>
        {" "}
        <div>{this.state.number}</div>{" "}
        <button onClick={store.dispatch({ type: "ADD" })}></button>{" "}
      </div>
    );
  }
}

createStore 实现

💡 createStore 的作用:通过 reducer / combineReducer 组合出来的总的 reducer,创建并初始化 store,返回 dispatch 发布方法, subscribe 订阅方法,getStore 获取仓库方法。

const createStore = (reducer, preloadedState, enhancer) => {
  let listeners = [];

  function getState() {
    return state;
  }
  
  // 收到 action,通过 reducer 函数重新计算新的 state
  function dispatch(action) {
    state = reducer(state, action);
    listeners.forEach((l) => l());
    return action;
  }
  
  /// 订阅函数 传入 listener, dispatch 的时候去调用每一个 listener
  function subscribe(listener) {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter((l) => l !== listener);
    };
  }

  dispatch({ type: "@@REDUX/INIT" });
  return {
    getState,
    dispatch,
    subscribe,
  };
};

实现 combineReducers

💡 作用和实现:第一:combineReducer 组合多个 reducer 返回一个全局的 reducer 第二: 通过调用所有的 reducer 组合成一个 全局的 store

function combineReducers(reducers) {
  return function (state = {}, action) {
    let nextState = {};
    for (let key in reducers) {
      nextState[key] = reducers[key](state[key], action);
    }
    return nextState;
  };
}

实现 applyMiddle

💡 作用和实现:第一:实现中间件的注册 ,第二: 实现中间件的级联。

function applyMiddleware(...middlewares) {
  return function (createStore) {
    return function (reducer, preloadedState) {
      //是创建计算改造后的store.dispatch的过程
      let store = createStore(reducer, preloadedState); //先创建一个仓库
      let dispatch;
      let middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action),
      };
      //chain=[promise,thunk,logger]
      let chain = middlewares.map((middleware) => middleware(middlewareAPI));
     
      dispatch = compose(...chain)(store.dispatch);
      return {
        ...store,
        dispatch,
      };
    };
  };
}

// 效果可以理解为这样:add3(add2(add1(args))) 内层函数的调用结果作为外层函数的参数。
function compose(...funcs) {
  return funcs.reduce(
    (a, b) =>
      (...args) =>
        a(b(...args))
  );
}

下面的一句代码实现了中间件的级联,如何实现的? 如果有三个中间件 middlewares=[promise,thunk,logger] 那么compose(...middlewares) 会变成像这样 (dispatch) ⇒ logger(thunk(promise(dispatch))) 所以,compose(...middileware)(store.dispatch) 返回的是第一个 logger 改造后的中间件,当我们在 UI 组件当中 dispatch 任何一个 action 实际上通过了所有中间件,直至到达目标中间件 (比如 thunk 中间件判断 action 是函数之后就停止),如果不是该中间件 通过 闭包 next(action) 调用下一个中间件。 至此,实现了中间件的级联。

      dispatch = compose(...middlewares)(store.dispatch);

实现 Redux 中间件

中间件的原理:核心原理就是劫持原来的 dispatch 方法,在原来的 dispatch 方法之前,之后做一些事情。而对于 redux 中间件由于要实现级联和实现中间件的compse,所以要按照一定的格式,外层函数接受两个参数 一个是 getState, dispatch , 返回两层嵌套函数, 第一层函数接受一个next 作为级联时使用的函数,第二层内层函数接收一个action,作为我们改造后的新的 dispatch 方法。

Thunk

function thunk({ getState, dispatch }) {
  return function (next) {
    //为了实现中间件的级联,调用下一个中间件
    return function (action) {
      //这才就是我们改造后的dispatch方法了
      if (typeof action === "function") {
        return action(dispatch, getState);
      }
      return next(action);
    };
  };
}

Promise

function promise({ getState, dispatch }) {
  return function (next) {
    //为了实现中间件的级联,调用下一个中间件
    //调用store.dispatch(action)
    return function (action) {
      //这才就是我们改造后的dispatch方法了
      if (action.then && typeof action.then === "function") {
        action.then(dispatch).catch(dispatch);
      } else if (action.payload && typeof action.payload.then === "function") {
        action.payload
          .then((result) => dispatch({ ...action, payload: result }))
          .catch((error) => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error); //返回失败的promise
          });
      } else {
        next(action);
      }
    };
  };
}