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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 14:47:10