useEffect 支持 async/await
在 useEffect 中正确使用异步函数的方法
问题
在 useEffect 回调函数中直接使用 async/await 会报错,因为 async 函数返回 Promise,而 useEffect 期望返回一个清理函数或 undefined。
解答
为什么不支持 async
useEffect 的返回值应该是一个清理函数,执行时机如下:
- 首次渲染不执行清理,在下一次渲染时清除上一次的副作用
- 组件卸载时执行清理操作
如果返回值是异步的,无法预知代码执行情况,容易出现难以定位的 Bug。因此 React 直接限制了 useEffect 回调函数不能是 async 函数。
解决方案
方法一:在内部创建异步函数
useEffect(() => {
const asyncFun = async () => {
setPass(await mockCheck());
};
asyncFun();
}, []);
方法二:使用 IIFE
useEffect(() => {
(async () => {
setPass(await mockCheck());
})();
}, []);
自定义 Hook
可以封装成自定义 Hook,参考 ahooks 的 useAsyncEffect 实现:
function useAsyncEffect(
effect: () => AsyncGenerator<void, void, void> | Promise<void>,
deps?: DependencyList,
) {
useEffect(() => {
let cancelled = false;
const execute = async () => {
await effect();
};
execute();
return () => {
cancelled = true;
};
}, deps);
}
这里的 cancelled 标识符用于中断执行。当用户频繁操作时,如果上一轮操作还未完成就开始下一轮,可以通过检查 cancelled 来停止已失效的操作。
清理机制的正确实现
不应该依赖异步函数来实现清理机制,而应该使用取消机制:
function useAsyncEffect(
effect: (isCanceled: () => boolean) => Promise<void>,
dependencies?: any[]
) {
return useEffect(() => {
let canceled = false;
effect(() => canceled);
return () => {
canceled = true;
};
}, dependencies);
}
使用示例:
useAsyncEffect(async (isCanceled) => {
const result = await doSomeAsyncStuff(stuffId);
if (!isCanceled()) {
// 仍然可以安全地执行副作用
}
}, [stuffId]);
这种方式通过传入 isCanceled 函数,让异步逻辑可以在关键节点检查是否已被取消,避免在 Hook 取消后仍对外部状态产生影响。
关键点
- useEffect 不支持 async 回调函数,因为需要返回清理函数而非 Promise
- 解决方法是在 useEffect 内部创建并调用异步函数
- 可以封装成自定义 Hook(如 useAsyncEffect)来优化使用体验
- 使用取消标识符(如
cancelled)来中断已失效的异步操作 - 清理机制应该是同步的取消机制,而不是依赖异步函数的延迟清理
目录