在 React 中使用类似于 redux 的中间件用于 useReducer
原文发表于2021年1月14日https://www.wisdomgeek.com。 如果你以前使用过 Redux,你就会知道中间件的概念。既然 useReducer 已经成为一个常用的 react hook,我们可能还想为 useReducer hook 复制中间件的想法。 如果您不了解中间件,中间件是在 reducer 发生状态转换之前或之后运行的函数。它使我们能够选择加入日志记录、崩溃报告、
原文发表于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问题的一部分,您可以阅读有关此方法的更多信息。最终的实现可以在这里找到。
- 我们可以创建一个自定义反应钩子,它在内部实现了 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 钩子中发生状态转换之前和之后运行中间件。如果您有任何疑问,请在评论中告诉我们。
更多推荐
所有评论(0)