官方答:在React18中请求数据的正确姿势(其他框架也适用)
大家好,我卡颂。
一些同学喜欢在useEffect
中请求初始数据,类似这样:
useEffect(() => {
fetch(xxx).then(data => setState(data.json()))
}, [])
但React18
并不推荐这种方式。
这么写有什么问题?如果不推荐这种方式,那么推荐的方式是什么呢?
本文来看看Dan
在reddit[1]是如何回答上述问题的。
这是一个普遍的问题
除了React
外,大部分以「组件」形式组织的前端框架,都有如下类似的API
:
effect
didMount
/didUpdate
如果有「初始化时请求数据」的需求,这类框架都会选择在上述回调函数内执行请求操作,并在数据返回后更新状态。
所以,这并不是React
独有的问题。相反,他很普遍。
之所以在React
中这么突出,是因为React
官方在引导开发者不要用这种形式书写代码(通过「严格模式下useEffect执行两次」放大这个问题)。
而React
之所以这么做,是为了项目的「性能」以及「UX」(User Experience,用户体验)。
下面我们来细聊这么做的影响。注意,这些影响同样适用于其他框架。
为什么不推荐这么写?
需要解决竞态问题
在useEffect
中请求数据要面临的第一个问题是「需要解决竞态问题」。
假设你有个组件User
,接收userID
作为props
,用userID
请求数据后展示用户信息。
下面是你的写法:
function User({userID}) {
const [data, setData] = useState(null);
useEffect(() => {
const res = await fetch(`https://xxx/${userID}/`);
setData(res.json());
}, [userID]);
if (data) {
return <div>{data.name}</div>;
}
return null;
}
这里有个开发阶段很难复现的bug
—— 如果userID
变化足够快,会发起多个不同的用户请求。
而最终展示哪个用户的数据,取决于哪个请求先返回。这就是「请求的竞态问题」。
点击返回按钮后重新请求数据
如果用户跳转到新的页面后,又通过浏览器回退按钮回到当前页面,并不能立刻看到他跳转前的页面。
相反,看到的可能是个白屏 —— 因为还需要重新执行useEffect
获取初始数据。
这个问题的本质原因是:没有初始数据的缓存。
CSR时的白屏时间
CSR
(Client-Side Rendering,客户端渲染)时在useEffect
中请求数据,在数据返回前页面都是白屏状态。
瀑布问题
如果父子组件都依赖useEffect
获取初始数据渲染,那么整个渲染流程如下:
父组件
mount
父组件
useEffect
执行,请求数据数据返回后重新渲染父组件
子组件
mount
子组件
useEffect
执行,请求数据数据返回后重新渲染子组件
可见,当父组件数据请求成功后子组件甚至还没开始首屏渲染。
这就是渲染中的瀑布问题 —— 数据像瀑布一样一级一级向下流动,流到的组件才开始渲染,很低效。
既然直接写useEffect
有这么多问题,那么推荐的方式是什么呢?
推荐的方式
在Meta
公司内部,基于Relay
驱动数据(但请求数据要求使用GraphQL
),所以这套架构比较难在社区普及开。
但是,现在社区已经有了成熟的「请求数据的方案」。
对于SSR
,可以使用Next.js
、Remix
接管数据请求。
对于CSR
,可以使用React Query
、useSWR
接管数据请求。
这些成熟的方案都致力于解决上述提到的问题。
如果不想使用这些方案,想自己写,可以参考React
新文档中下面两篇文章:
使用effect同步数据[2]
你可能不需要使用effect[3]
想看中文的同学,可以看我写的总结 —— React新文档:不要滥用effect哦 原创
总结
本文我们聊了React18
之后「不推荐的请求数据的方式」以及「推荐的请求数据」的方式。
其中「不推荐的请求数据的方式」不仅存在于React
中,很多前端框架都有这样的问题。
参考资料
reddit: https://www.reddit.com/r/reactjs/comments/vi6q6f/what_is_the_recommended_way_to_load_data_for/
[2]使用effect同步数据: https://beta.reactjs.org/learn/synchronizing-with-effects#fetching-data
[3]你可能不需要使用effect: https://beta.reactjs.org/learn/you-might-not-need-an-effect#fetching-data