0Pricing
React Academy · 课时

竞态条件与 Effect 清理

在 useEffect 中使用 AbortController,在重新渲染时取消过期的 fetch 请求。

竞态条件与 Effect 清理 是 CoddyKit 上的免费 React Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Academy 课程共包含 4 节课。

欢迎

在本课中,您将学习在获取数据的 React 副作用中如何发生竞态条件,以及如何使用 AbortController 进行清理来消除这些问题。

什么是竞态条件

当两个异步操作以不可预测的顺序完成时,就会发生竞态条件。在 React 中,如果用户快速在不同项目之间导航,项目 A 的缓慢获取请求可能会在项目 B 的请求之后才完成,从而显示过时的数据。

问题:过时的获取响应

如果没有清理,并且组件在旧的获取请求完成前因新的 ID 重新渲染,那么两个请求都会运行。较旧且较慢的请求可能会覆盖状态中的较新数据。
useEffect(() => {
  fetch('/api/item/' + id)
    .then(r => r.json())
    .then(data => setData(data)); // may run AFTER a newer fetch!
}, [id]);

修复方法 1:忽略标志

一种简单的修复方法是在副作用中设置布尔值 `ignore` 标志。清理函数会将其设为真值。获取回调在调用 setState 前检查该标志。
useEffect(() => {
  let ignore = false;
  fetch('/api/item/' + id)
    .then(r => r.json())
    .then(data => { if (!ignore) setData(data); });
  return () => { ignore = true; };
}, [id]);

修复方法 2:AbortController

AbortController 是现代标准。在副作用中创建一个控制器,将其信号传给获取请求,并在清理函数中中止请求。获取请求会抛出 AbortError,您可以忽略它。
useEffect(() => {
  const controller = new AbortController();
  fetch('/api/item/' + id, { signal: controller.signal })
    .then(r => r.json())
    .then(data => setData(data))
    .catch(err => {
      if (err.name === 'AbortError') return; // expected
      setError(err.message);
    });
  return () => controller.abort();
}, [id]);

使用 AbortController 与异步/等待

在 useEffect 中使用异步函数和 try/catch。请在 catch 代码块中忽略 AbortError。
useEffect(() => {
  const ctrl = new AbortController();
  async function load() {
    try {
      const res = await fetch('/api/' + id, { signal: ctrl.signal });
      const json = await res.json();
      setData(json);
    } catch (e) {
      if (e.name !== 'AbortError') setError(e.message);
    }
  }
  load();
  return () => ctrl.abort();
}, [id]);

始终需要清理

即使竞态条件不太可能发生(例如挂载时只获取一次数据),也请始终添加清理。在 React 18 StrictMode 中,副作用会执行两次——如果没有清理,第一个被放弃的请求就会污染状态。

useEffect 的异步陷阱

useEffect 的回调不能是 `async`。请在副作用中定义一个异步函数并立即调用它,或者使用带有 .then()/.catch() 的普通函数。
useEffect(() => {
  // useEffect callback is NOT async
  async function fetchData() { /* ... */ }
  fetchData(); // call it
  return () => ctrl.abort();
}, [id]);

库会为您处理这些问题

React Query、SWR 和 RTK Query 都会自动处理竞态条件、中止和清理。使用这些库时,您不需要手动编写 AbortController 代码。

测试竞态条件

要手动测试竞态条件:打开网络 DevTools,将网络限制为“慢速 3G”,然后快速点击不同项目。没有清理时,您会看到过时数据一闪而过。使用 AbortController 后,只会显示最后选中的项目。

快速检查

在 useEffect 的清理函数中调用 controller.abort() 会做什么?

回顾

当过时的获取响应在较新的请求之后更新状态时,就会发生竞态条件。请使用忽略标志或 AbortController 进行修复。在 useEffect 的清理中始终中止或忽略请求。React Query 等库会自动处理这些问题。

下一课

下一课:**使用 useTransition 实现非阻塞界面更新**——您将把开销较大的状态更新包裹在 startTransition 中,以保持界面响应迅速。

常见问题解答

「竞态条件与 Effect 清理」课时是免费的吗?

是的 — 「竞态条件与 Effect 清理」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Academy 课程共包含 4 节课。

「竞态条件与 Effect 清理」这节课中我会学到什么?

在 useEffect 中使用 AbortController,在重新渲染时取消过期的 fetch 请求。 你通过在浏览器中直接运行的动手代码来练习 React Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 React Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 React Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「竞态条件与 Effect 清理」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 React Academy 课中编写并运行代码吗?

能。每节 React Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 竞态条件与 Effect 清理
  2. 使用 useTransition 实现非阻塞式界面更新
  3. 使用 useDeferredValue 实现输入防抖
  4. 使用 Suspense 划分数据与代码边界
← 返回 React Academy