标准答案
- 请求函数应和组件展示逻辑分离,统一处理 baseURL、鉴权头、错误格式和取消能力。
- 组件层需要表达 loading、empty、success、error、retry、refreshing 等状态,而不是只用一个布尔值。
- 缓存要区分服务端数据和客户端 UI 状态,列表、详情、分页和搜索通常需要不同 key。
- 错误处理要分层:接口错误转成稳定结构,页面决定如何提示,登录失效和权限错误可以走全局处理。
- 当出现重复请求、缓存失效、后台刷新和乐观更新需求时,应考虑 React Query / TanStack Query。
题目解析
这道题考的是工程组织能力。API 请求不是在 useEffect 里 fetch 一下就结束,还要考虑状态生命周期。
服务端数据和客户端状态的心智不同:服务端数据有过期、失效、重试和并发刷新问题。
请求层越统一,组件越容易只关心“数据能否展示”和“用户下一步能做什么”。
代码示例
下面用自定义 Hook 把列表请求状态收敛到一个明确的返回结构。
TSX
function useUsers() {
const [state, setState] = useState<{
status: 'idle' | 'loading' | 'success' | 'error'
data: User[]
error?: string
}>({ status: 'idle', data: [] })
useEffect(() => {
const controller = new AbortController()
setState(prev => ({ ...prev, status: 'loading', error: undefined }))
fetchUsers({ signal: controller.signal })
.then(data => setState({ status: 'success', data }))
.catch(error => {
if (error.name !== 'AbortError') {
setState({ status: 'error', data: [], error: normalizeError(error) })
}
})
return () => controller.abort()
}, [])
return state
}常见误区
- 每个组件都手写 fetch、loading、error,最后错误格式和重试行为不一致。
- 把接口返回数据直接当作永久客户端状态,不考虑过期、刷新和缓存失效。
- 请求取消和竞态没有处理,旧请求返回后覆盖了新页面状态。