在React函数式组件中,useEffect 是处理副作用的利器。然而,当它遇上数据获取,我们常常会写出这样的“面条代码”:
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch('/api/user');
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
这段代码虽然功能完整,但存在几个明显问题:
- 重复劳动:每个数据请求都需要重复设置 loading、error 状态。
- 竞态条件:在快速切换的组件中,可能先发后至的请求会覆盖最新结果。
- 功能缺失:缺乏缓存、错误重试、页面聚焦重新请求等高级功能。
解决方案:拥抱“SWR”模式
“SWR”这个名字来自于 HTTP RFC 5861 提出的缓存策略:stale-while-revalidate(陈旧内容重新验证)。其核心思想是:首先立即返回缓存中的陈旧数据,然后在后台发送请求以获取最新数据,最后用新数据更新缓存。
在具体实践中,我们可以使用像 swr 或 TanStack Query 这样的库来轻松实现。
使用 swr 库重构
上面的代码可以简化为:
import useSWR from 'swr';
const fetcher = (...args) => fetch(...args).then(res => res.json());
function Profile() {
const {
data, error, isLoading } = useSWR('/api/user', fetcher);
if (error) return <div>请求失败</div>;
if (isLoading) return <div>加载中...</div>;
return <div>你好,{
data.name}!</div>;
}
看,代码变得多么简洁!不仅如此,你还免费获得了:
- 自动缓存与重复数据删除:相同 key 的请求会自动缓存并只发送一次。
- 智能重试:请求失败时会自动重试。
- 焦点重新获取:窗口重新聚焦时自动刷新数据。
总结
useEffect 本身没有错,但它是一个底层的、通用的副作用工具。对于数据获取这种高层级、高度模式化的需求,使用像 SWR 这样的声明式库是更明智的选择。它能让你用更少的代码,获得更健壮、用户体验更好的应用。下次当你准备在 useEffect 中写 fetch 时,不妨先考虑一下 SWR。