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

使用React Hooks时状态无法更新?求自定义API Hook修复方案

我来帮你排查下代码里的几个关键问题,正是这些问题导致状态无法正确更新:

问题1:then方法的回调使用错误

你当前代码里的这一行存在明显问题:

fetch(url).then(dispatch({ type: "fetching", loading: true }))

then方法需要接收函数作为参数,但你直接执行了dispatch并把它的返回值(undefined)传给了then,这会导致两个问题:

  • 还没发起请求就触发了fetching action,失去了loading状态的时效性
  • 后续链式调用因为没有正确的Promise延续,逻辑会出现异常

正确的做法是在发起请求之前就dispatch fetching action(因为fetch是异步操作,这样能立刻把loading设为true):

useEffect(() => {
  // 先触发fetching,设置loading为true
  dispatch({ type: "fetching", loading: true, url });
  axios(url)
    .then(res => res.data)
    .then(data => dispatch({ type: "success", loading: false, data, url }))
    .catch(err => dispatch({ type: "fail", loading: false, err, url }));
}, [url]);

问题2:Reducer的状态结构与初始化不匹配

你的初始状态是{ loading: false },但Reducer里试图以url作为key存储每个请求的状态,这会引发两个问题:

  • 初始时state[url]是undefined,展开...currentState会导致异常
  • 你在Reducer里解构const { url, data, err } = action;,但之前dispatch的action里根本没有传入url字段

修正后的Reducer代码:

const Reducer = (state, action) => {
  const { type, url, data, err, loading } = action;
  // 给当前url的状态设置默认空对象,避免undefined报错
  const currentState = state[url] || {};
  
  switch (type) {
    case "fetching":
      return { ...state, [url]: { ...currentState, loading } };
    case "success":
      return { ...state, [url]: { ...currentState, loading, data } };
    case "fail":
      return { ...state, [url]: { ...currentState, loading, err } };
    default:
      return state;
  }
};

问题3:useReducer的初始状态错误

因为我们是按url来区分多个请求的状态,所以初始状态应该是一个空对象,而不是单一的{ loading: false }:

const [state, dispatch] = useReducer(reducer, {});

修正后的完整代码

import { useEffect, useReducer } from "react";
import axios from "axios";

const useApiHook = (url, reducer) => {
  const [state, dispatch] = useReducer(reducer, {});
  
  useEffect(() => {
    // 发起请求前先设置loading为true
    dispatch({ type: "fetching", loading: true, url });
    axios(url)
      .then(res => res.data)
      .then(data => dispatch({ type: "success", loading: false, data, url }))
      .catch(err => dispatch({ type: "fail", loading: false, err, url }));
  }, [url, reducer]);

  // 直接返回当前url对应的状态,组件使用更便捷
  return state[url] || { loading: false };
};

const Reducer = (state, action) => {
  const { type, url, data, err, loading } = action;
  const currentState = state[url] || {};
  
  switch (type) {
    case "fetching":
      return { ...state, [url]: { ...currentState, loading } };
    case "success":
      return { ...state, [url]: { ...currentState, loading, data } };
    case "fail":
      return { ...state, [url]: { ...currentState, loading, err } };
    default:
      return state;
  }
};

const Comp = () => {
  const url = "https://randomuser.me/api/";
  const sample = useApiHook(url, Reducer);
  
  return (
    <div>
      {sample.loading ? <p>Loading...</p> : <pre>{JSON.stringify(sample.data, null, 2)}</pre>}
      {sample.err && <p>Error: {sample.err.message}</p>}
    </div>
  );
};

额外优化说明

  • 把import fetch from "axios"改成import axios from "axios",避免命名混淆(axios的默认导出是axios实例,不是fetch函数)
  • 给useEffect的依赖数组加上reducer,避免闭包导致的逻辑异常
  • Hook返回值直接返回当前url对应的状态,组件使用时无需再处理整个state对象

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 07:27:19