本章属于 React 教程的核心路线。建议先通读概念模型,再手写最小示例,最后用练习验证自己能否独立复现。
本章目标
本章学习 fetch、loading、error、AbortController、stale response prevention,并初步认识 Suspense 概念。
阅读本章时,请把每个 API 放回“数据如何变成界面、用户操作如何改变数据”的主线中理解。React 的学习重点不是背诵语法,而是建立可预测的渲染模型。
- 识别本章概念在真实组件中的位置
- 能写出一个最小可运行示例
- 能解释常见错误为什么发生
概念模型
异步 UI 至少有三类状态:加载中、成功、失败。请求可能乱序返回,所以组件需要取消旧请求或忽略过期响应。
你可以把 React 程序看成一棵由组件组成的元素树。每次输入数据、状态或外部系统发生变化,React 会重新计算需要展示的 React element,再用高效的方式更新浏览器中的 DOM。
最小示例
课程详情组件在 lessonId 改变时发起请求,用 AbortController 取消旧请求,并用局部标记防止 stale response 写入。
import { useEffect, useState } from "react";
type Lesson = { title: string };
export function LessonDetails({ lessonId }: { lessonId: string }) {
const [lesson, setLesson] = useState<Lesson | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
const controller = new AbortController();
let stale = false;
setLoading(true);
setError("");
fetch(`/api/lessons/${lessonId}`, { signal: controller.signal })
.then((response) => response.json())
.then((data) => {
if (!stale) setLesson(data);
})
.catch((reason) => {
if (!stale && reason.name !== "AbortError") setError("加载失败");
})
.finally(() => {
if (!stale) setLoading(false);
});
return () => {
stale = true;
controller.abort();
};
}, [lessonId]);
if (loading) return <p>loading...</p>;
if (error) return <p>{error}</p>;
return <h2>{lesson?.title}</h2>;
}
type LessonNote = { title: string; done: boolean };\nconst note: LessonNote = { title: 'React 教程', done: false };
规则与陷阱
规则的价值在于让多人维护同一个项目时仍能预测行为。下面这些限制看似细碎,实际都在保护组件的输入、输出和生命周期边界。
- 不要只处理成功路径,loading 和 error 必须有明确界面。
- 组件卸载或参数变化时应取消旧请求。
- 不要让旧响应覆盖新响应。
- Suspense 是声明式异步边界概念,普通项目仍常用显式状态起步。
练习
练习应当先小后大:先验证一个概念,再把它接入完整页面。不要在没有理解失败原因时复制更复杂的代码。
- 给 fetch 示例增加 error 展示。
- 快速切换 lessonId,验证旧请求不会覆盖新数据。
- 写出 loading、error、empty、success 四种状态的 UI。