在副作用中用 fetch 加载远程数据,并干净利落地对加载状态和错误状态进行建模。
在 Kodokon 中打开本课一个网络请求既耗时又可能失败:你的界面必须反映这些现实。专业的惯例是追踪三份不同的状态:接收到的数据、一个加载中布尔值,以及一个可能出现的错误。请求放在 useEffect 内部,因为联系服务器是一个副作用。
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("https://api.example.com/users")
.then((res) => {
if (!res.ok) throw new Error("HTTP " + res.status);
return res.json();
})
.then((data) => setUsers(data))
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, []);在渲染中,按顺序处理这些情况:先是加载中,然后是错误,最后是数据。这些提前返回让代码保持可读,并保证用户总能看到某些连贯的东西:一个等待指示器、一条失败信息,或者预期的列表。
if (loading) return <p>Loading…</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);还有最后一个陷阱:如果某个依赖项变化了(比如一个 userId),一个新请求就会发出去,而旧请求可能仍在途中。如果旧请求最后才响应,它就会覆盖掉新数据。经典的对策是:一个由清理函数设为 true 的 ignore 标志,用来丢弃过时的响应。
useEffect(() => {
let ignore = false;
setLoading(true);
fetch("/api/users/" + userId)
.then((res) => res.json())
.then((data) => {
if (!ignore) setUser(data);
})
.finally(() => {
if (!ignore) setLoading(false);
});
return () => {
ignore = true;
};
}, [userId]);catch 从未触发。为什么?ignore 标志,是用来做什么的?