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

自定义useFetch Hook触发请求后无法访问返回数据该如何优化?

现有方案的核心问题

  • 冗余的本地状态:你不需要额外维护displayedItems这个副本状态,useFetch返回的data可以直接在渲染逻辑中使用,多一层状态反而会因为React异步更新的特性,出现刚setState就打印拿到旧值的问题。
  • 重复的useEffect逻辑:自动触发初始请求的逻辑可以封装到Hook内部,不需要每次使用时都额外写两个useEffect。
  • 缺少必要的状态管理:原有Hook没有加载状态、错误状态,实际使用时很难处理加载中、请求失败的边界场景。

优化后的useFetch实现

我们给Hook增加可选参数、状态和优化点,同时保留你需要的手动调用fetchData的能力:

import { useState, useCallback, useEffect } from 'react';

const useFetch = (initialUrl = null, immediate = true) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  // 用useCallback包裹,保证fetchData引用稳定,避免不必要的重渲染/重复执行
  const fetchData = useCallback(async (url) => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`请求错误: ${response.status}`);
      const jsonData = await response.json();
      setData(jsonData);
      return jsonData; // 支持链式调用单独处理单次请求结果
    } catch (err) {
      setError(err.message);
      setData(null);
      throw err; // 支持catch捕获单次请求错误
    } finally {
      setLoading(false);
    }
  }, []);

  // 内部封装初始自动请求逻辑,外部无需额外写useEffect
  useEffect(() => {
    if (immediate && initialUrl) {
      fetchData(initialUrl);
    }
  }, [initialUrl, immediate, fetchData]);

  return { data, loading, error, fetchData };
};

两种常见场景的使用方式

场景1:页面加载自动请求,同时支持手动触发(比如刷新、重新提交)

不需要额外写任何useEffect,传初始url即可自动请求:

const ListPage = () => {
  // 传入初始接口地址,默认immediate为true,页面加载自动触发请求
  const { data: itemList, loading, error, fetchData } = useFetch('/api/getList');

  // 事件回调中手动触发请求,比如点击刷新按钮
  const handleRefresh = () => {
    fetchData('/api/getList');
    // 如需单独处理这次请求的结果,直接用.then或await即可:
    // fetchData('/api/getList').then(res => { console.log('刷新拿到的新数据:', res) })
  };

  // 边界状态处理
  if (loading) return <div>加载中...</div>;
  if (error) return <div>请求失败:{error}</div>;

  return (
    <div>
      <button onClick={handleRefresh}>刷新列表</button>
      {/* 直接用返回的data渲染即可,不需要额外存本地状态 */}
      {itemList?.map(item => <div key={item.id}>{item.content}</div>)}
    </div>
  );
};

场景2:仅事件触发才发起请求(比如搜索、表单提交)

关闭自动触发开关,只在需要的时候调用fetchData:

const SearchPage = () => {
  const [keyword, setKeyword] = useState('');
  // 不传初始url,immediate设为false,不会自动触发请求
  const { data: searchResult, loading, fetchData } = useFetch(null, false);

  const handleSearch = async () => {
    if (!keyword) return;
    try {
      const res = await fetchData(`/api/search?q=${keyword}`);
      console.log('搜索成功,结果:', res);
      // 这里可以直接写搜索成功后的额外逻辑,不需要监听data变化
    } catch (err) {
      console.log('搜索失败:', err);
    }
  };

  return (
    <div>
      <input value={keyword} onChange={e => setKeyword(e.target.value)} />
      <button onClick={handleSearch}>搜索</button>
      {searchResult?.map(item => <div key={item.id}>{item.title}</div>)}
    </div>
  );
};

其他疑问解答

你之前加data真值判断才能正常运行,是因为初始状态下请求还没完成,data还是初始值,加判断可以避免给displayedItems设空值,优化后的Hook因为有loading和error状态,已经可以优雅处理这些边界场景,不需要额外加判断。

内容的提问来源于stack exchange,提问作者embracethefuture

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 08:15:03