React自定义useFetch钩子两次调用仅一次获取数据问题求助
React自定义useFetch钩子多次调用失效问题解决
问题描述
我是一名React新手,目前遇到问题:无法通过两次调用自定义useFetch钩子请求两个不同接口并获取数据,进而在不同组件中使用。
App.jsx代码
import { Route, Routes } from 'react-router-dom'; import useFetch from './hooks/useFetch'; import { BlogContentPage, Homepage } from './pages'; import AboutPage from './pages/AboutPage'; export default function App() { let { loading, data, error } = useFetch('http://localhost:1337/api/blogs?populate=*'); // 这个接口能正常获取数据 let { data: authors } = useFetch('http://localhost:1337/api/authors?populate=*'); // 这个无法获取,移除上面的调用后它就正常工作了(!?!?!) if (loading) return <p>Loading...</p>; if (error) return <p>Error!</p>; console.log(authors); // 这里输出null.. return ( <div> <Routes> <Route path='/' element={<Homepage blogs={data ? data : ''} />}></Route> <Route path='/blog/:id' element={<BlogContentPage blogs={data ? data : ''} />}></Route> <Route path='/about' element={<AboutPage authors={authors ? authors : ''} />}></Route> </Routes> </div> ); }
自定义useFetch钩子代码
import { useEffect, useState } from 'react'; const useFetch = (url) => { const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const fetchData = async () => { setLoading(true); try { const res = await fetch(url); const json = await res.json(); setData(json); setLoading(false); } catch (error) { setError(error); setLoading(false); } }; fetchData(); }, [url]); return { loading, error, data }; }; export default useFetch;
预期:每次调用该useFetch钩子都能正常工作。
问题原因
核心问题在App组件的状态判断逻辑:
- 你只检查了第一个useFetch返回的
loading状态,当第一个请求完成后,loading变为false,组件立即退出loading状态开始渲染,但此时第二个请求可能还在进行中,authors仍然是初始的null值。 - 错误处理也只针对第一个请求,第二个请求的错误无法被捕获。
解决方案
方案1:合并多个请求的状态判断
修改App组件,同时判断两个请求的loading和error状态:
import { Route, Routes } from 'react-router-dom'; import useFetch from './hooks/useFetch'; import { BlogContentPage, Homepage } from './pages'; import AboutPage from './pages/AboutPage'; export default function App() { // 给每个请求的状态重命名,避免冲突 const { loading: blogsLoading, data: blogsData, error: blogsError } = useFetch('http://localhost:1337/api/blogs?populate=*'); const { loading: authorsLoading, data: authorsData, error: authorsError } = useFetch('http://localhost:1337/api/authors?populate=*'); // 只要有一个请求未完成,就显示loading if (blogsLoading || authorsLoading) return <p>Loading...</p>; // 只要有一个请求出错,就显示错误 if (blogsError || authorsError) return <p>Error!</p>; console.log(authorsData); // 现在能正常打印数据 return ( <div> <Routes> <Route path='/' element={<Homepage blogs={blogsData || ''} />}></Route> <Route path='/blog/:id' element={<BlogContentPage blogs={blogsData || ''} />}></Route> <Route path='/about' element={<AboutPage authors={authorsData || ''} />}></Route> </Routes> </div> ); }
方案2:在子组件中独立调用useFetch(推荐)
遵循React组件职责单一原则,将数据请求下沉到需要数据的子组件中,每个组件只负责自己的数据逻辑:
比如修改AboutPage组件:
// AboutPage.jsx import useFetch from '../hooks/useFetch'; export default function AboutPage() { const { loading, data: authors, error } = useFetch('http://localhost:1337/api/authors?populate=*'); if (loading) return <p>Loading authors...</p>; if (error) return <p>Failed to load authors!</p>; return ( <div> {/* 在这里渲染authors数据 */} </div> ); }
同样,Homepage和BlogContentPage也可以各自调用useFetch获取blogs数据,这样App组件会更简洁,每个请求的状态也能独立处理。
可选优化:给useFetch添加请求取消逻辑
当前钩子在组件卸载时如果请求未完成,会导致内存泄漏,添加AbortController可以避免这个问题:
import { useEffect, useState } from 'react'; const useFetch = (url) => { const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const controller = new AbortController(); const signal = controller.signal; const fetchData = async () => { setLoading(true); try { const res = await fetch(url, { signal }); // 检查HTTP响应状态 if (!res.ok) throw new Error(`Request failed with status ${res.status}`); const json = await res.json(); setData(json); setLoading(false); } catch (error) { // 忽略取消请求的错误 if (error.name !== 'AbortError') { setError(error); setLoading(false); } } }; fetchData(); // 组件卸载时取消请求 return () => controller.abort(); }, [url]); return { loading, error, data }; }; export default useFetch;
内容的提问来源于stack exchange,提问作者Ivan Graciarena
相关产品推荐
相关产品推荐

