React useEffect添加fetchingPokemons依赖后无渲染、功能失效如何解决?
问题原因
- 你定义的
fetchingPokemons是写在自定义Hook顶层的普通函数,每次Hook执行(也就是依赖它的组件重渲染、或者Hook内部state变更触发重渲染)的时候,都会生成一个全新的函数引用 - 你把它放到
useEffect的依赖数组后,每次函数引用变化都会触发useEffect重新执行 - 每次
useEffect重新执行前,会先执行上一次的清理函数,也就是abortController.abort(),会直接取消上一次还没完成的请求 - 同时请求完成后调用
setData/setLoading会触发Hook重渲染,又会生成新的fetchingPokemons引用,再次触发useEffect执行,最终陷入「发起请求→请求完成触发重渲染→请求被取消→重新发起请求」的死循环,要么一直停留在loading状态,要么请求被取消后报错,自然没有内容渲染 - 额外问题:你当前的
abortController也定义在Hook顶层,每次重渲染都会生成新的实例,清理函数调用的abort根本不是对应当前正在执行请求的控制器,原本的防内存泄漏逻辑其实是无效的
修复方案
方案1:直接把请求逻辑写到useEffect内部(最推荐,无额外依赖问题)
const usePokemons = (): IusePokemons => { const [isLoading, setLoading] = useState(false); const [data, setData] = useState<IPokemon[]>(); const [error, setError] = useState(''); useEffect( () => { const abortController = new AbortController(); // 把请求逻辑直接写在effect内部 const fetchingPokemons = async () => { setLoading(true); try { const response = await fetch(urls.pokemonDataUrl, { signal: abortController.signal, }); const json = await response.json(); setData(json.results); // 此处json.results无需额外await } catch (error) { // 过滤掉请求取消的报错 if (error.name !== 'AbortError') { setError(error.message); } } finally { setLoading(false); } }; fetchingPokemons(); return () => { abortController.abort(); }; }, // 依赖为空,只会在Hook挂载时执行一次 [], ); return {data, error, isLoading}; }; export default usePokemons;
方案2:用useCallback包裹fetchingPokemons固定函数引用
如果你的请求逻辑需要在effect之外也调用,可以用useCallback包裹函数,固定它的引用:
const usePokemons = (): IusePokemons => { const [isLoading, setLoading] = useState(false); const [data, setData] = useState<IPokemon[]>(); const [error, setError] = useState(''); const fetchingPokemons = useCallback(async (abortSignal: AbortSignal) => { setLoading(true); try { const response = await fetch(urls.pokemonDataUrl, { signal: abortSignal, }); const json = await response.json(); setData(json.results); } catch (error) { if (error.name !== 'AbortError') { setError(error.message); } } finally { setLoading(false); } }, []); // 依赖为空,引用永远不变 useEffect( () => { const abortController = new AbortController(); fetchingPokemons(abortController.signal); return () => { abortController.abort(); }; }, [fetchingPokemons], // 引用不变,effect只会执行一次 ); return {data, error, isLoading, fetchingPokemons}; };
内容的提问来源于stack exchange,提问作者MatkoMilic
相关产品推荐
相关产品推荐

