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

React+TypeScript如何从Axios拦截器触发Redux Dispatch事件

React+TypeScript:在Axios拦截器中触发Redux Dispatch

问题背景

作为React+TypeScript新手,希望通过Axios拦截器统一管理API调用前后的Redux状态(比如加载状态),通过请求头参数custom_loader控制是否触发加载器,同时不需要将API响应存储到Redux中。之前尝试在拦截器中调用封装的ShowLoader函数时触发错误,原因是违反了React Hook的使用规则。

解决方案

核心思路

Axios拦截器不属于React组件树,无法使用useDispatch这类Hook。正确的做法是直接导入Redux Store实例,调用其全局的dispatch方法来触发action。

步骤1:修改Axios拦截器代码

更新src/Helpers/AxiosInterceptors.ts,直接导入Store并在拦截器中触发加载状态的action:

import {
  AxiosError,
  AxiosInstance,
  AxiosResponse,
  InternalAxiosRequestConfig,
} from "axios";
import store from "../Redux/store";
import { showLoader, hideLoader } from "../Redux/Loader/slice";

const onRequest = (
  config: InternalAxiosRequestConfig
): InternalAxiosRequestConfig => {
  // 根据请求头判断是否显示加载器
  if (config.headers?.custom_loader === "SHOW_LOADER") {
    store.dispatch(showLoader());
  }
  console.info(`[request] [${JSON.stringify(config)}]`);
  return config;
};

const onRequestError = (error: AxiosError): Promise<AxiosError> => {
  // 请求失败时强制隐藏加载器
  store.dispatch(hideLoader());
  console.error(`[request error] [${JSON.stringify(error)}]`);
  return Promise.reject(error);
};

const onResponse = (response: AxiosResponse): AxiosResponse => {
  // 请求成功后隐藏加载器
  if (response.config.headers?.custom_loader === "SHOW_LOADER") {
    store.dispatch(hideLoader());
  }
  console.info(`[response] [${JSON.stringify(response)}]`);
  return response;
};

const onResponseError = (error: AxiosError): Promise<AxiosError> => {
  // 响应失败时隐藏加载器
  store.dispatch(hideLoader());
  console.error(`[response error] [${JSON.stringify(error)}]`);
  return Promise.reject(error);
};

export function setupInterceptorsTo(
  axiosInstance: AxiosInstance
): AxiosInstance {
  axiosInstance.interceptors.request.use(onRequest, onRequestError);
  axiosInstance.interceptors.response.use(onResponse, onResponseError);
  return axiosInstance;
}

步骤2:删除无用的DispatchActions.ts文件

由于直接使用Store的全局dispatch,之前封装的DispatchActions.ts文件已无存在必要,可直接删除。

步骤3:优化组件类型定义

避免使用any类型,改用Redux自动生成的RootState和AppDispatch提升类型安全性:

Employee.tsx

import { useEffect, useState } from "react";
import { IEmployeeList } from "../../Models/Employee";
import { EmployeeList } from "./EmployeeList";
import { EmployeeService } from "./EmployeeServices";
import Button from "react-bootstrap/Button";
import { useDispatch, useSelector } from "react-redux";
import { trackRefreshCount } from "../../Redux/RefreshCount/slice";
import type { RootState, AppDispatch } from "../../Redux/store";

export const Employee: React.FC = () => {
  const getEmployeesList = EmployeeService();
  const [empTblList, setEmpTblList] = useState<IEmployeeList[]>([]);
  const dispatch = useDispatch<AppDispatch>();
  const { loader, trackRefreshCountSlice } = useSelector(
    (state: RootState) => state
  );

  useEffect(() => {
    SetEmployeeList();
  }, []);

  const SetEmployeeList = async () => {
    const data = await getEmployeesList("");
    setEmpTblList(data);
  };

  return (
    <div>
      <Button
        disabled={loader.isLoading}
        variant="primary"
        onClick={() => {
          dispatch(trackRefreshCount());
          SetEmployeeList();
        }}
      >
        Refresh ({trackRefreshCountSlice.refreshCount})
      </Button>
      <EmployeeList EmpTableList={empTblList} />
    </div>
  );
};

EmployeeList.tsx

import { IEmployeeList } from "../../Models/Employee";
import Table from "react-bootstrap/Table";
import { useSelector } from "react-redux";
import SkeletonTable from "../../Component/Skeleton/SkeletonTable";
import type { RootState } from "../../Redux/store";

export const EmployeeList = (props: { EmpTableList: IEmployeeList[] }) => {
  const { EmpTableList } = props;
  const { loader } = useSelector((state: RootState) => state);

  return (
    <div className="table-container">
      <Table responsive>
        <thead>
          <tr>
            <th>Name</th>
            <th>Active</th>
            <th>Birthday</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          {loader.isLoading ? (
            <SkeletonTable Column={3} Row={10} />
          ) : (
            EmpTableList.map((d: IEmployeeList, index: number) => (
              <tr key={index}>
                <td>{d.name}</td>
                <td>{d.isActive ? "Active" : "Inactive"}</td>
                <td>{d.birthday}</td>
                <td>&nbsp;</td>
              </tr>
            ))
          )}
        </tbody>
      </Table>
    </div>
  );
};

关于Thunk的补充说明

你提到不想存储API响应到Redux所以未使用Thunk,但Thunk的核心是允许编写异步逻辑触发action,完全不需要将响应存入Redux。示例如下:

// 示例Thunk函数(不存储响应到Redux)
import { createAsyncThunk } from "@reduxjs/toolkit";
import API from "../Helpers/HttpClient";
import { showLoader, hideLoader } from "./Loader/slice";

export const fetchEmployees = createAsyncThunk(
  "employees/fetch",
  async (_, { dispatch }) => {
    dispatch(showLoader());
    try {
      const response = await API.get("uow", {
        headers: { custom_loader: "SHOW_LOADER" },
      });
      return response.data; // 返回数据给组件处理,不存入Redux
    } finally {
      dispatch(hideLoader());
    }
  }
);

在组件中使用:

const SetEmployeeList = async () => {
  const data = await dispatch(fetchEmployees()).unwrap();
  setEmpTblList(data);
};

这种方式也能实现加载状态管理,若你倾向于统一通过拦截器管理所有API的加载状态,全局Store dispatch方案更合适。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 13:59:54