React中useEffect内fetch函数被调用两次的问题求助
问题:React中useEffect内的fetch被调用两次
我在React应用里碰到个问题:useEffect钩子里的fetch函数会被调用两次,搞不懂原因。相关代码如下:
useEffect(() => { if (auth.userDTO && router.isReady) { let url = paymentId ? `user/tickets?paymentId=${paymentId}` : 'user/tickets'; fetchWithBaseUrl(url, { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + auth.userDTO.token, }, }).then(async (response) => { if (response.ok) { const data = await response.json(); setMyTickets(data); } }); } }, [auth.userDTO, router.isReady]);
我已经把auth.userDTO和router.isReady设为useEffect的依赖项,但某些场景下fetch还是会触发两次。
我自己琢磨了两种可能:
- 初始渲染:fetch在初始渲染时调用,这是预期的。
- 依赖变化:如果
auth.userDTO或router.isReady在组件生命周期里发生变化,useEffect会再次执行,导致fetch多调用一次。
我想知道有没有办法避免这次额外的fetch调用,同时保证只有当auth.userDTO和router.isReady都为真值时才执行fetch。有没有人遇到过类似问题?该怎么解决?有哪些最佳实践或优化方案能避免这种情况?
解决方案
1. 排查依赖的“隐性变化”
你看到的两次调用,大概率是因为auth.userDTO每次都是新对象(比如每次登录后返回的是新的引用),哪怕内容没变化,React也会判定依赖更新,触发useEffect。可以在依赖里只监听必要的字段(比如auth.userDTO.token),减少不必要的触发:
// 只监听token而不是整个userDTO useEffect(() => { if (auth.userDTO?.token && router.isReady) { let url = paymentId ? `user/tickets?paymentId=${paymentId}` : 'user/tickets'; fetchWithBaseUrl(url, { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + auth.userDTO.token, }, }).then(async (response) => { if (response.ok) { const data = await response.json(); setMyTickets(data); } }); } }, [auth.userDTO?.token, router.isReady]);
2. 添加请求去重逻辑
如果依赖确实会变化,但你不想重复发起请求,可以用状态记录是否已经完成过有效请求:
const [hasFetched, setHasFetched] = useState(false); useEffect(() => { if (auth.userDTO && router.isReady && !hasFetched) { let url = paymentId ? `user/tickets?paymentId=${paymentId}` : 'user/tickets'; fetchWithBaseUrl(url, { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + auth.userDTO.token, }, }).then(async (response) => { if (response.ok) { const data = await response.json(); setMyTickets(data); setHasFetched(true); } }); } // 依赖变化时重置状态,允许重新请求 return () => setHasFetched(false); }, [auth.userDTO, router.isReady]);
3. 检查StrictMode的影响
如果你的应用在React StrictMode下运行,开发环境中useEffect会被执行两次(这是React的故意设计,用来检测副作用问题)。生产环境下不会有这个问题,你可以先注释掉StrictMode测试,如果两次调用消失了,那就是这个原因,无需修改代码。
最佳实践
- 尽量避免在依赖中传递复杂对象,优先监听基本类型值(比如ID、token)。
- 对于网络请求,用
AbortController处理竞态问题,避免依赖变化时旧请求覆盖新请求的结果:
useEffect(() => { const controller = new AbortController(); const signal = controller.signal; if (auth.userDTO && router.isReady) { let url = paymentId ? `user/tickets?paymentId=${paymentId}` : 'user/tickets'; fetchWithBaseUrl(url, { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + auth.userDTO.token, }, signal, }).then(async (response) => { if (response.ok) { const data = await response.json(); setMyTickets(data); } }).catch(err => { if (err.name !== 'AbortError') { // 处理其他错误 } }); } return () => controller.abort(); }, [auth.userDTO, router.isReady]);
内容的提问来源于stack exchange,提问作者János
相关产品推荐
相关产品推荐

