You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 05:45:08