大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
数据获取与错误边界
这一课你将能做到
- 用状态机思想处理 loading / success / error
- 在 Effect 里请求时防止「过期响应」写回界面
- 知道错误边界大概接住什么、接不住什么
为什么要学
真实页面要向后端要数据:课程标题、进度、题库。
网络慢、失败、返回空,都要有界面,而不是白屏或转圈到永远。
人话:请求不是「发出去就一定成功」;界面要诚实说出三种情况——还在等、等到了、失败了。
本站 lib/api 封装了 fetch;本课用手写状态机,把模式看清。Next 服务端取数能少写样板,思想相同。
先建立图像
idle → loading → success
↘ error →(可重试,回到 loading)
另外:用户已经切到另一门课,上一门的响应才到——必须丢掉(dead 旗标)。
跟着做
步骤 1:手写状态(完整可抄)
import { useEffect, useState } from "react";
export function TrackTitle({ slug }) {
const [state, setState] = useState({ status: "loading" });
useEffect(() => {
let dead = false;
setState({ status: "loading" });
fetch("/api/tracks/" + slug)
.then((r) => {
if (!r.ok) throw new Error("HTTP " + r.status);
return r.json();
})
.then((data) => {
if (!dead) setState({ status: "ok", data });
})
.catch((err) => {
if (!dead) {
setState({
status: "error",
message: String(err.message || err),
});
}
});
return () => {
dead = true;
};
}, [slug]);
if (state.status === "loading") return <p>加载中…</p>;
if (state.status === "error") {
return <p>失败:{state.message}</p>;
}
return <h1>{state.data.title}</h1>;
}
空数据:ok 之后列表 length 为 0,显示「暂无」,不要假装还在加载。
步骤 2:401 与业务失败
HTTP 401:引导登录,而不是只写「失败」。
400 校验错误:把后端返回的消息展示在表单附近。
这些都是「错误 UI」,不是只打日志。
步骤 3:错误边界(概念,别当万能网)
类组件或库提供的 Error Boundary:子组件渲染期间抛错时,显示降级 UI(「这块坏了」),避免整页白屏。
它接不住:
- 事件处理里的
throw - 异步
fetch里的多数错误 - 服务端很多错误(看框架)
网络错误仍要自己 catch 并 setState。边界是渲染期的安全带,不是 try/catch 替代品。
步骤 4:重试
错误 UI 上放按钮:再次 setState({ status: "loading" }) 或把 retryCount 放进 Effect 依赖。
连点重试同样要考虑 in-flight。
步骤 5:错误与空态
if (error) return <p role="alert">{error}</p>;
if (!rows.length) return <p>暂无课程</p>;
把 error / empty / list 分开,避免「失败还显示旧列表」却不提示。
讲清楚要点
- 永远给用户反馈:加载中、失败原因、空数据。
- slug 变化要重拉,并取消过期写回。
- Next 服务端取数可少写这类样板,但思想相同。
- 错误边界 ≠ 捕获 fetch。
- 成功态也不要闪旧数据:切 slug 时先回到 loading,或明确标「刷新中」。
你可能会懵的地方
-
为何有时闪一下旧标题?
没在依赖变化时把状态打回 loading,旧ok还在。 -
Strict Mode 开发打两次请求?
与 Effect 课相同;cleanup 把dead设上即可。 -
SWR / React Query?
缓存、重试、去重它们能做。入门先会手写状态机,再用库。 -
把 JSON 直接当 JSX 孩子?
对象不能当孩子。渲染字段。 -
Error Boundary 吃掉事件错误?
主要吃渲染期错误;事件处理器里要自己 try/catch 或设 error state。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 无 loading | 像卡死 | 明确加载态 |
| 无 dead 旗标 | 错乱 setState | cleanup |
| 只 console 不展示 | 用户懵 | 错误 UI |
| 靠错误边界接住 fetch | 接不住 | catch + 状态 |
| 失败仍显示上一门课当成功 | 误导 | 切走就 loading/清空 |
| 只 setData 不处理 !ok | 静默坏数据 | 检查 res.ok |
| 加载中仍可重复狂点 | 重复请求 | disabled / abort |
自测三问
- 三种状态各应给用户看什么?空列表算哪一种?
dead旗标在防什么?- 错误边界能不能接住点击事件里的 throw?能不能接住 fetch 失败?
小结一句话
取数要有状态机与过期保护;错误要可见,边界只是渲染期兜底。
练习:故意把 fetch 的路径写错,确认页面出现失败文案而不是一直「加载中」。再快速切换两个 slug,确认不会把先发出、后返回的那一门标题盖到新课上。
动手加深
Client fetch 课表:loading → ok/error;切换课程时 abort 上一次。再读 Next 轨服务端取数对比。
跟着做(加厚 · react-data)
步骤:最小可运行对照
在 Vite 或 Next 练习页粘贴本课核心示例,改一个输入,确认屏幕变化符合预期。再故意写一个错法表里的写法,看控制台/界面如何坏,然后改回。
每个概念都问:state 在谁手里?UI 哪一块会因它重绘?
操作顺序:打开对应文件 → 改一处可观察点(文案、类型、className 或分支)→ 保存看结果 → 复原或留下有意义的小改动。
步骤:错法故意练一次
先按「用 jQuery 式直接改 DOM 冒充完成」做 30 秒(或在注释里写出若这样做会怎样),再改成「改数据(state/props)让 UI 推导出来」。 目的不是留坑,而是让失败现象进长期记忆。
你可能会懵的地方(加厚)
-
热更新后状态丢了?
正常:整树重挂会重置 state。用 key 或外提状态保留;开发期别被吓到。 -
和文档示例不一致?
以你锁的 React 大版本 + react.dev 为准;旧博客的 class 组件示例仅作历史阅读。 -
这课和前后课怎么接?
回看大纲上一课的小结一句、下一课标题;用自己的话写「本课产出什么可观察结果」。写不出来就重做跟着做。
错法对照(加厚)
| 错法 | 问题 | 正解 |
|---|---|---|
| 用 jQuery 式直接改 DOM 冒充完成 | 假完成/埋雷 | 改数据(state/props)让 UI 推导出来 |
| 只看不敲 | 第二天全忘 | 至少跑通本课步骤 |
| 跳过自测三问 | 以为会了 | 闭卷答再对稿 |
延伸
- react.dev 错误处理相关;对照本站
lib/api - 下一课:综合练习,并通向 Next.js。