获取数据
一个显示服务器数据的组件在首次渲染时还没有那些数据。它需要请求数据、等待,然后在收到响应后显示内容,或者在请求失败时处理错误。这一章讲的是 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 手册在 异步 JavaScript 这一章从基础讲起;这里的所有内容都是建立在那些基础之上的。
useEffect 是一个 hook,它在 React 渲染完组件后运行代码,而不是在渲染过程中运行。你给它提供一个函数和一个依赖数组,依赖数组里是那些值改变时应该让 effect 重新运行的值,这里只有 url。Profile 从两个状态开始:data 用来保存结果,error 用来保存任何出错信息。effect 开头,setData(null) 和 setError(null) 清除上一次运行留下的任何东西。这很重要,有两个原因:没有它的话,当 url 改变时,组件会继续显示旧的个人资料而不是"Loading...",因为 data 还保存着上一次成功的结果;而且如果一个请求在早前的请求失败后成功了,旧的错误永远不会被清除,因为没有东西会把 error 设回 null。然后 fetch 返回一个响应 Promise。fetch 只在网络失败时拒绝这个 Promise,所以 404 或 500 仍然会作为响应对象正常 resolve,这就是为什么第一个 .then 会检查 r.ok 并在响应到达 .json() 之前抛出错误。跳过这个检查的话,失败的请求会直接流入 data,好像它成功了一样。一旦检查通过,.json() 读取响应体,结果通过 setData 放入 data。如果这些中的任何一个被拒绝,.catch 会把问题放入 error。在 JSX 中,像处理其他状态一样检查 error 和 data:错误渲染一条消息,还没有数据时渲染加载消息,有数据时渲染真实的 UI。
这里没有单独的 loading 变量。只要 data 和 error 都仍然是 null,组件就处于请求已发出但还没收到响应的状态,所以 if (!data) return <p>Loading...</p> 自己就能处理这种情况。有些组件会添加一个独立的 isLoading 布尔值来获得更细致的控制,但从你已有的状态推导出 loading 状态通常更简单,而且少了一个需要保持同步的值。
effect 中的 active 变量是一个清理标志。useEffect 可以返回一个函数,React 会在 effect 再次运行时和组件卸载时调用这个函数。这里,返回的函数把 active 设为 false,.then 和 .catch 在调用 setData 或 setError 之前都会检查它。没有这个检查的话,当 url 改变时或组件离开屏幕时,仍在进行中的请求最终会 resolve 并试图更新已经不属于正在显示的内容的状态。这个标志把一个迟到的响应变成了无操作,而不是一个 bug。
像这样手工获取数据值得理解,因为它是几乎所有其他东西都建立在其上的模式。在真实的应用中,你通常会用 React Query 这样的数据获取库,或者框架内置的数据加载(Next.js 和类似的框架在路由层处理这个),而不是为每个请求写一遍 useEffect 和几个状态。那些工具会为你处理缓存、重试和下面的计时问题。这一章中的模式就是它们构建在其上的。
useEffect 中,把 data 和 error 放在状态中,在 JSX 中检查它们来决定显示什么。加载就是这两个都还是空的那一刻。 active 标志阻止迟到的响应在不再显示那些数据的组件上设置状态。
下一章:Refs 和 DOM,处理那些需要 DOM 节点本身的工作的逃生舱。

