原文发表于2021年1月14日https://www.wisdomgeek.com。

如果你以前使用过 Redux,你就会知道中间件的概念。既然 useReducer 已经成为一个常用的 react hook,我们可能还想为 useReducer hook 复制中间件的想法。

如果您不了解中间件,中间件是在 reducer 发生状态转换之前或之后运行的函数。它使我们能够选择加入日志记录、崩溃报告、发出异步 API 请求等功能。

在这篇文章中,我们将为 useReducer react hook 创建一个中间件。如果你想了解更多关于 hook 和 reducer 的一般信息,请参阅我们之前关于useReducer React hook的帖子。

为 useReducer 创建中间件的可能方法

我们可以通过以下两种方式之一来实现中间件功能:

1.编写一个类似于redux的applyMiddleware函数。这个函数将第一个参数作为reducer,我们将中间件作为数组传递给第二个参数。这看起来像这样:

const useMyReducer = applyMiddleware(useReducer, [logging, thunks, ...]);

作为此 GitHub问题的一部分,您可以阅读有关此方法的更多信息。最终的实现可以在这里找到。

  1. 我们可以创建一个自定义反应钩子,它在内部实现了 useReducer 并为我们提供了将中间件作为参数传递的功能。

我们将在这篇博文中讨论第二种方法。第一种方法也是可以接受的。但我的观点是,如果我们从 hooks 的角度思考,我们应该在 hooks 方面向前发展,而不是坚持 redux 模式。

单个中间件使用Reducer

让我们首先定义我们将要构建的这个自定义反应钩子的样子。我们将从单个中间件开始。稍后,我们将通过使我们的实现通用化来向上移动到多个中间件。

我们的 useReducer 中间件将接收一个 reducer 作为参数,以及初始状态。它还将中间件作为另一个参数。因此,我们的钩子将采用以下形式:

const useReducerWithMiddleware = (reducer,
      initialState,
      middleware,
    ) => {
      const [state, dispatch] = useReducer(reducer, initialState);
      // TODO: middleware logic 
      return [state, dispatch];
    };

进入全屏模式 退出全屏模式

对于中间件函数的调用,在 useReducer 声明之后的钩子内部调用它是不够的。我们希望在每次调用 dispatch 时调用中间件函数。因此,我们需要返回一个修改后的函数,而不是直接返回 dispatch。

我们可以通过使用高阶函数来解决这个问题。我们将通过围绕它创建一个高阶函数来增强调度函数。然后我们将从我们的钩子返回高阶函数。

    const useReducerWithMiddleware = (reducer,
      initialState,
      middleware,
    ) => {
      const [state, dispatch] = useReducer(reducer, initialState);
      const dispatchUsingMiddleware = (action) => {
        middleware(action);
        dispatch(action);
      }
      return [state, dispatchUsingMiddleware];
    };

进入全屏模式 退出全屏模式

由于我们从自定义钩子返回扩展调度函数,因此我们确保每当调用者调用我们的自定义中间件用于 useReducer 钩子时都会调用中间件。

我们甚至可以在中间件调用中添加其他信息,例如状态。

    const useReducerWithMiddleware = (reducer,
      initialState,
      middleware,
    ) => {
      const [state, dispatch] = useReducer(reducer, initialState);
      const dispatchUsingMiddleware = (action) => {
        middleware(action, state);
        dispatch(action);
      }
      return [state, dispatchUsingMiddleware];
    };

进入全屏模式 退出全屏模式

多个中间件供使用Reducer

让我们扩展我们之前的中间件实现,以便 useReducer 接受多个中间件函数作为数组。

由于所有的中间件函数都应该在调用 dispatch 之前被调用,我们将遍历它们。然后,我们将调用 dispatch。

    const useReducerWithMiddleware = (reducer,
      initialState,
      middlewares,
    ) => {
      const [state, dispatch] = useReducer(reducer, initialState);
      const dispatchUsingMiddleware = (action) => {
      middlewares.map((middleware) => middleware(action, state));
        dispatch(action);
      }
      return [state, dispatchUsingMiddleware];
    };

进入全屏模式 退出全屏模式

如果我们在做一些异步中间件,我们将不得不调整这个逻辑来使用 async/await。但是我们将把这部分放在这篇文章的范围之外。

但是,如果我们想要在状态转换之后执行的中间件,也就是调度调用之后呢?

状态改变后的中间件

如果你认为我们会在 dispatch 之后为中间件创建另一个输入数组来执行,那你是绝对正确的!

但是,如果您考虑在调度调用之后立即调用这些函数,例如:

    const useReducerWithMiddleware = (reducer,
      initialState,
      middlewares,
      afterDispatchMiddleWares
    ) => {
      const [state, dispatch] = useReducer(reducer, initialState);
      const dispatchUsingMiddleware = (action) => {
        middlewares.map((middleware) => middleware(action, state));
        dispatch(action);
        afterDispatchMiddleWares.map((middleware) => middleware(action, state));
      }
      return [state, dispatchUsingMiddleware];
    };

进入全屏模式 退出全屏模式

然后可悲的是,这行不通。

你能想出一个理由吗?

这是因为 dispatch 异步更新状态。

可以做些什么呢?

我们可以等待状态更新,然后有一个回调函数来处理它。我们可以使用useEffect hook来实现这一点。

    const useReducerWithMiddleware = (reducer,
      initialState,
      middlewares,
      afterDispatchMiddleWares
    ) => {
      const [state, dispatch] = useReducer(reducer, initialState);
     useEffect(() => {
        afterDispatchMiddleWares.map((middleware) => middleware(action, state));
      }, [afterDispatchMiddleWares]);

      const dispatchUsingMiddleware = (action) => {
        middlewares.map((middleware) => middleware(action, state));
        dispatch(action);
      }
      return [state, dispatchUsingMiddleware];
    };

进入全屏模式 退出全屏模式

但是我们无法再访问 useEffect 中的操作了。因此,我们需要通过使用 useRef 挂钩来使用 ref 实例变量。我们将在调用 dispatch 之前将 action 的值写入 ref 变量。然后它的值将在效果内部可供我们使用。

    const useReducerWithMiddleware = (reducer,
      initialState,
      middlewares,
      afterDispatchMiddleWares
    ) => {
      const [state, dispatch] = useReducer(reducer, initialState);
      const currentRef = React.useRef();
      useEffect(() => {
        if (!currentRef.current) return;
        afterDispatchMiddleWares.map((middleware) => middleware(currentRef.current, state));
      }, [afterDispatchMiddleWares, state]);

      const dispatchUsingMiddleware = (action) => {
        middlewares.map((middleware) => middleware(action, state));
        currentRef.current = action;
        dispatch(action);
      }
      return [state, dispatchUsingMiddleware];
    };

进入全屏模式 退出全屏模式

这样就完成了我们使用 useReducer 应用中间件的实现。我们现在可以在 React 钩子中发生状态转换之前和之后运行中间件。如果您有任何疑问,请在评论中告诉我们。

Logo

React社区为您提供最前沿的新闻资讯和知识内容

更多推荐