异步操作(API 调用)有多种可能的结果,因此您必须建模 不仅仅是数据 — 您需要表示加载、成功和错误状态。正确建模这一点可以防止 UI bug,例如显示过时数据或无反馈。
天真的方法及其缺陷
js
[data, setData] = ();
[loading, setLoading] = ();
[error, setError] = ();
独立的标志允许矛盾的组合(加载 和 错误 和 数据同时)这不应该发生 — 这是造成混淆 UI bug 的根源。
// ✅ a single `status` makes states mutually exclusive
const [state, setState] = useState({ status: "idle", data: null, error: null });
// status is one of: 'idle' | 'loading' | 'success' | 'error'
async function load() {
setState({ status: "loading", data: null, error: null });
try {
const data = await api.get();
setState({ status: "success", data, error: null });
} catch (e) {
setState({ status: "error", data: null, error: e.message });
}
}
单个 status 字段使状态 互斥 — 您不能同时处于加载和错误状态。渲染变成一个干净的 switch:
switch (state.status) {
case "loading": return <Spinner />;
case "error": return <Error message={state.error} />;
case "success": return <List data={state.data} />;
default: return <Idle />;
}
idle — hasn't started yet (vs loading)
empty — succeeded but no results (show "No items" not a blank screen)
refetching— has old data AND is loading new (show data + a subtle spinner)
// React Query / SWR model all of this for you
const { data, isLoading, isError, error } = useQuery({ queryKey: ["x"], queryFn });
服务器状态库提供这些状态(加上缓存、重新获取、stale-while-revalidate)开箱即用 — 通常比手动实现更好。
正确建模异步状态 — 作为单个状态而不是松散的布尔值 — 消除了整个类别的 UI bug(矛盾状态、缺少加载/错误/空反馈),并使渲染逻辑清晰完整。
识别完整的状态集(idle、loading、success、error、empty、refetching)会导致更好的用户体验,而了解服务器状态库处理所有这些是保持数据获取代码简单而健壮的关键。