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

带useEffect的自定义React Hook无法在非组件函数中使用的解决方案咨询

问题本质

你遇到的问题核心是违反了React的Hook调用规则:所有Hook必须在React组件的顶层渲染作用域中调用,不能在事件回调、条件分支、循环或者其他嵌套函数中调用。你原来的useAxios设计是调用即自动通过useEffect触发请求,只能适配页面加载时自动发起请求的场景,无法支持手动触发(比如表单提交时发起请求)的需求。


解决方案:改造useAxios为可手动触发的版本

调整Hook的设计,移除自动执行请求的逻辑,改为返回请求触发函数,同时保留loading、data等状态和取消请求的能力,同时兼容自动请求和手动请求两种场景:

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

export default function useAxios({ url, defaultData = {}, method = "GET", autoRequest = false} ) {
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  // 存储取消请求的令牌,处理竞态和卸载清理
  const cancelTokenSource = useRef(null);

  const triggerRequest = useCallback(async (requestData = defaultData) => {
    // 取消之前未完成的重复请求,避免状态混乱
    if (cancelTokenSource.current) {
      cancelTokenSource.current.cancel('重复请求,已取消上一次');
    }
    cancelTokenSource.current = axios.CancelToken.source();

    setLoading(true);
    setError(null);
    try {
      const response = await axios({
        url,
        method,
        // 根据请求类型自动适配参数位置
        data: method.toUpperCase() !== 'GET' ? requestData : undefined,
        params: method.toUpperCase() === 'GET' ? requestData : undefined,
        cancelToken: cancelTokenSource.current.token
      });
      const resData = response.data;
      setData(resData);
      return resData;
    } catch (err) {
      // 过滤主动取消的请求报错
      if (!axios.isCancel(err)) {
        setError(err);
        throw err;
      }
    } finally {
      setLoading(false);
    }
  }, [url, method, defaultData]);

  // 如果开启自动请求,组件挂载时自动执行
  useEffect(() => {
    if (autoRequest) triggerRequest();
  }, [autoRequest, triggerRequest]);

  // 组件卸载时清理未完成的请求
  useEffect(() => {
    return () => {
      if (cancelTokenSource.current) {
        cancelTokenSource.current.cancel('组件卸载,取消未完成请求');
      }
    };
  }, []);

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

事件回调中使用的示例

export default function MyComponent() {
  // 组件顶层调用Hook,拿到触发函数和状态
  const { data, loading, triggerRequest } = useAxios({
    url: "/my-end-point",
    method: "POST"
  });

  async function handleSubmit(e) {
    e.preventDefault();
    // 事件回调中仅调用触发函数,传入动态的请求参数
    const submitResult = await triggerRequest({
      testInput: e.target.testInput.value
    });
    // 可直接在这里拿到请求结果做后续逻辑处理
    console.log('提交返回结果:', submitResult);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="testInput" />
      <button type="submit" disabled={loading}>
        {loading ? '提交中' : '提交'}
      </button>
      {data && <div>返回结果:{JSON.stringify(data)}</div>}
    </form>
  );
}

方案优势

  • 完全符合React Hook调用规则,不会再有Hook调用位置的报错
  • 同时兼容两种使用场景:如果需要页面加载时自动发请求,只需要在调用useAxios时传入autoRequest: true即可,不用修改原有自动请求的业务代码
  • 自带重复请求拦截、竞态处理、卸载时请求清理的能力,稳定性更强

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 18:15:03