在React应用里,组件常需调用接口拿到数据再渲染页面。若只存数据不处理加载和异常,用户可能看到空白或控制台警告。合理地把异步结果映射到状态是基本功。
为什么需要区分多种状态
一次数据请求通常包含三个阶段:正在请求、成功返回、发生错误。如果只用单个data状态,无法告诉用户当前处于哪一步。推荐用三个状态字段分别表达。
- data:存放接口返回的数据
- loading:布尔值,表示是否正在请求
- error:存放错误信息或null
基础写法示例
下面代码展示在函数组件中如何通过useState与useEffect完成一次异步获取:
import React, { useState, useEffect } from 'react';
function UserList() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
// 异步函数写在内部
async function fetchData() {
try {
const res = await fetch('https://ipipp.com/api/users');
const json = await res.json();
if (isMounted) {
setData(json);
setLoading(false);
}
} catch (err) {
if (isMounted) {
setError(err.message);
setLoading(false);
}
}
}
fetchData();
// 清理函数,防止组件卸载后更新状态
return () => {
isMounted = false;
};
}, []);
if (loading) return <p>加载中...</p>;
if (error) return <p>出错:{error}</p>;
return (
<ul>
{data && data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default UserList;
使用AbortController取消请求
当组件快速切换时,旧请求可能仍在进行。通过AbortController可在卸载时中断fetch。
useEffect(() => {
const controller = new AbortController();
fetch('https://ipipp.com/api/users', { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') setError(err);
});
return () => controller.abort();
}, []);
常见错误与注意点
直接在useEffect传异步函数
useEffect的回调应返回清理函数或undefined,若直接写async函数会返回Promise,React会警告。应像前面例子在内部定义async再调用。
忘记处理卸载后的状态更新
未做isMounted或abort控制,控制台可能出现Can't perform a React state update on an unmounted component警告。
小结
把异步逻辑拆分为加载、数据、错误三个状态,配合清理机制,就能在React组件中稳定地完成数据获取与界面更新。