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

删除缓存后MERN项目服务器仍返回304状态码(RTK Query场景)

问题:MERN项目中RTK Query请求返回304状态码,Postman请求返回200

我在开发MERN项目,使用RTK Query处理请求与缓存。从React发起请求时,服务器控制台持续返回304状态码,但通过Postman请求同一接口则返回200。我尝试删除浏览器缓存、本地存储及Cookie,问题仍未解决,服务器端始终显示该状态码。

服务器控制台304状态码截图

相关代码

基础空服务(emptySlice.js)

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { baseUrl } from "../utils/base_url";

export const emptySlice = createApi({
  reducerPath: "api",
  baseQuery: fetchBaseQuery({ baseUrl: `${baseUrl}/api/` }),
  tagTypes: ["Serviceman"],
  endpoints: (build) => ({}),
});

servicemenSlice.js

import { emptySlice } from "./emptySlice";

export const serviceMenSlice = emptySlice.injectEndpoints({
  endpoints: (build) => ({
    getServiceMen: build.query({
      query: () => "serviceman",
      providesTags: ["Serviceman"],
    }),
  }),
});

export const { useGetServiceMenQuery } = serviceMenSlice;

index.js

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
import { ApiProvider } from "@reduxjs/toolkit/dist/query/react";
import { emptySlice } from "./services/emptySlice";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <BrowserRouter>
    <ApiProvider api={emptySlice}>
      <App />
    </ApiProvider>
  </BrowserRouter>
);

// If you want to start measuring performance in your app, pass a function
reportWebVitals();

Landing.jsx(问题触发组件)

import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import Header from "../../components/Header/Header";
import ServiceSearchBar from "../../components/ServiceSearchBar/ServiceSearchBar";
import ServiceCategoryList from "../../components/ServiceCategoryList/ServiceCategoryList";
import { useDispatch, useSelector } from "react-redux";
import { getServices } from "../../features/service/serviceSlice";
import MainContentWrapper from "../../components/MainContentWrapper/MainContentWrapper";
import { getAllServiceMen } from "../../features/serviceMan/serviceManSlice";
import ServiceMan from "../../components/ServiceMan/ServiceMan";
import Footer from "../../components/Footer/Footer";
import PageWrapper from "../../components/PageWrapper/PageWrapper";
import Loading from "../../components/Loading/Loading";
import Badge from "../../components/Badge/Badge";
import { useGetServiceMenQuery } from "../../services/servicemenSlice";
import { useGetServicesQuery } from "../../services/serviceSlice";

const Landing = () => {
  const {
    data: serviceMen,
    isLoading,
    isFetching,
    isError,
    error,
  } = useGetServiceMenQuery();

  const {
    data: services,
    isLoading: isLoadingServices,
    isFetching: isFetchingServices,
    isError: isErrorServices,
  } = useGetServicesQuery();

  const [activeServiceMen, setActiveServiceMen] = useState([]);

  if (isLoading || isFetching) {
    return <Loading />;
  }

  if (isError) {
    return <h1>{error}</h1>;
  }

  const serviceMenList = serviceMen.map((serviceMan) => (
    <ServiceMan key={serviceMan._id} serviceMan={serviceMan} />
  ));

  const activeServices = services.filter((service) => service.active);

  return (
    <PageWrapper>
      <Header />
      <MainContentWrapper>
        <ServiceSearchBar />
        <section className="grid grid-col lg:grid-cols-4 w-full gap-5 items-start mb-10">
          <div className="flex col-span-full lg:col-span-1 flex-col gap-2">
            <Badge text="Services" number={activeServices.length} />
            <ServiceCategoryList services={activeServices} />
          </div>

          <div className="col-span-full lg:col-span-3 w-full justify-center">
            <Badge text="Service Men" number={activeServiceMen.length} />
            <div className="flex flex-col gap-4 mt-5 pr-6">
              {activeServiceMen.length > 0 ? (
                { serviceMenList }
              ) : (
                <div className="flex justify-center items-center">
                  <p className="text-black">No service man yet</p>
                </div>
              )}
            </div>
          </div>
        </section>
      </MainContentWrapper>
      <Footer />
    </PageWrapper>
  );
};

Landing.propTypes = {};

export default Landing;

解决方案

1. 全局禁用浏览器缓存(修改fetchBaseQuery配置)

RTK Query的fetchBaseQuery默认遵循浏览器缓存策略,会触发协商缓存返回304。可以通过添加请求头强制浏览器跳过缓存:

修改emptySlice.js中的fetchBaseQuery配置:

export const emptySlice = createApi({
  reducerPath: "api",
  baseQuery: fetchBaseQuery({ 
    baseUrl: `${baseUrl}/api/`,
    headers: {
      'Cache-Control': 'no-cache, no-store, must-revalidate',
      'Pragma': 'no-cache',
      'Expires': '0'
    }
  }),
  tagTypes: ["Serviceman"],
  endpoints: (build) => ({}),
});

2. 针对单个查询强制刷新

如果不需要全局禁用缓存,可以给特定查询添加强制刷新配置,或者在组件调用时指定刷新规则:

方式一:在查询定义中配置

修改servicemenSlice.js的getServiceMen:

getServiceMen: build.query({
  query: () => "serviceman",
  providesTags: ["Serviceman"],
  extraOptions: {
    forceRefetch: true // 强制每次请求都从服务器拉取最新数据
  }
}),

方式二:在组件调用时传入参数

修改Landing.jsx中的查询调用:

const {
  data: serviceMen,
  isLoading,
  isFetching,
  isError,
  error,
} = useGetServiceMenQuery(undefined, { 
  refetchOnMountOrArgChange: true // 组件挂载或参数变化时自动刷新
});

3. 检查服务器端缓存配置

304状态码由服务器返回,说明后端可能设置了缓存头。需要检查Express等后端框架的缓存配置,确保动态接口不被缓存:

例如在Express接口中添加禁用缓存的响应头:

app.get('/api/serviceman', (req, res) => {
  // 禁用缓存
  res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
  res.setHeader('Pragma', 'no-cache');
  res.setHeader('Expires', '0');
  
  // 业务逻辑
  res.json(serviceMenData);
});

4. 手动触发RTK Query缓存刷新

如果需要在特定时机(比如数据更新后)刷新缓存,可以调用refetch方法:

const { data: serviceMen, refetch } = useGetServiceMenQuery();

// 在需要刷新的时机调用,比如组件挂载时
useEffect(() => {
  refetch();
}, []);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 06:27:14