获取数据
一个展示服务器数据的组件首次渲染时还没有那些数据。它需要请求数据、等待响应,然后在数据到达时展示结果,或在请求失败时处理错误。这章讲的就是 React 应用处理这个问题的模式:在 effect 中执行 fetch,用 useState 保存结果、加载状态和错误。
这是一个根据 URL 获取用户信息的 Profile 组件。组件先渲染,然后才向服务器请求数据,这正是 useEffect 存在的原因:
function Profile({ url }) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
useEffect(() => {
let active = true
setData(null)
setError(null)
fetch(url)
.then(r => {
if (!r.ok) throw new Error(`Request failed: ${r.status}`)
return r.json()
})
.then(d => { if (active) setData(d) })
.catch(e => { if (active) setError(e) })
return () => { active = false }
}, [url])
if (error) return <p>Failed to load</p>
if (!data) return <p>Loading...</p>
return <h1>{data.name}</h1>
}如果 fetch、promise 或 .then 还不熟悉,可以先看 JavaScript 教程中 Async JavaScript 的部分,这里的内容都建立在那个基础之上。
useEffect 是一个 hook,它在 React 渲染完组件之后运行代码,而不是在渲染过程中运行。你给它传一个要运行的函数和一个依赖数组,这个数组列出什么值变化时应该重新运行 effect,这里只有 url。Profile 开始时有两个状态:data 存放结果,error 存放任何出错信息。在 effect 开头,setData(null) 和 setError(null) 清空前一次运行留下的任何数据。这很重要,原因有两个:如果不这样做,当 url 改变时,会继续显示旧的个人资料而不是"Loading...",因为 data 里仍然存着上次成功的结果;如果一个请求在前面一个失败之后成功了,也永远清不掉旧的错误,因为没有什么会把 error 重新设回 null。然后 fetch 返回一个 promise 来处理响应。fetch 只在网络故障时 reject 这个 promise,所以 404 或 500 仍然会正常 resolve 为一个响应对象,这就是为什么第一个 .then 要检查 r.ok 并在响应传给 .json() 前抛出错误。跳过这个检查的话,失败的请求会流进 data 仿佛它成功了一样。一旦检查通过,.json() 读取响应体,结果通过 setData 流入 data。如果这期间任何地方 reject 了,.catch 会把问题放进 error。在 JSX 中,error 和 data 像任何其他状态一样被检查:有错误就渲染一条信息,还没有数据就渲染加载信息,有数据就渲染真正的 UI。
这里没有单独的加载变量。只要 data 和 error 都还是 null,组件就处在请求已发出但还未收到响应的状态中,所以 if (!data) return <p>Loading...</p> 自己就能覆盖这个情况。有些组件会加一个专门的 isLoading 布尔值来做更细致的控制,但从你已有的状态推导加载状态往往更简单,而且少了一个需要保持同步的值。
effect 里的 active 变量是一个清理标志。useEffect 可以返回一个函数,React 会在 effect 即将再次运行时以及组件卸载时调用那个函数。这里返回的函数把 active 设为 false,.then 和 .catch 都会在调用 setData 或 setError 之前检查它。没有这个检查的话,当 url 改变或组件离开屏幕时,仍在进行中的请求最终会 resolve 并试图更新那个不再属于正在显示内容的状态。这个标志让一个迟到的响应变成无操作,而不是一个 bug。
像这样手动 fetch 值得理解,因为这是几乎所有其他东西都建立在其上的模式。在真实的应用中,你通常会用像 React Query 这样的数据获取库,或者框架内置的数据加载(Next.js 这样的框架在路由层处理),而不是为每个请求都手写 useEffect 和几个状态。那些工具会为你处理缓存、重试和下面讲的时序问题。这章讲的模式正是它们建立的基础。
useEffect 里,把 data 和 error 放在状态里,在 JSX 中检查它们来决定显示什么。加载就是两个都还是空的那一刻。active 标志阻止迟到的响应在组件不再显示那些数据时设置状态。 接下来:Refs 和 DOM,这是那些需要 DOM 节点本身的工作的逃生舱口。

