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

React页面切换前组件重渲染引发无效API调用问题修复求助

问题描述

我有一个React应用,可在automatic和manual两个页面间切换,两个页面展示不同数据。流程为:先从服务器获取对应页面的批次列表,再生成表格,且为每个批次再次调用服务器获取批次数据。例如调用"/api/manual/list_batches"获取manual批次列表,再用批次名调用"/api/manual/get_batch?batch_name=foo_manual_batch"获取单批次数据。

切换到automatic页面时,预期会清空旧表格并调用"/api/automatic/list_batches"获取新批次列表,再为新批次调用对应API,但实际在获取新批次列表前,表格检测到URL中的mode从manual变为automatic,会尝试用旧批次名调用"/api/automatic/get_batch?batch_name=foo_manual_batch",导致错误。页面功能正常,但存在不必要的无效API调用。

复现代码

App组件

import React from "react";
import { Route, Switch } from "react-router-dom";

import BatchesPage from "./BatchesPage";
import Navigation from "./Navigation";
import "./styles.css";
export default function App() {
  return (
    <div>
      <Switch>
        <Route exact path="/">
          <Navigation />
        </Route>
        <Route exact path="/:mode">
          <Navigation />
          <BatchesPage />
        </Route>
      </Switch>
    </div>
  );
}

BatchesPage组件

import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";

import BatchesTable from "./BatchesTable";
import { fetchJson } from "./model";

export default function BatchesPage() {
  const [batchesNames, setBatchesNames] = useState([]);
  const { mode } = useParams();
  const getBatchesNamesCmd = `/api/${mode}/list_batches`;

  // 根据mode加载automatic或manual批次名称
  useEffect(() => {
    fetchJson(getBatchesNamesCmd)
      .then((jsonData) => {
        setBatchesNames(jsonData.sort().reverse());
      })
      .catch((error) => {
        console.error(`error occure: ${error}`);
      });
  }, [getBatchesNamesCmd]);

  return batchesNames ? (
    <div>
      {mode === "automatic" ? (
        <div className="horizontal-container">
          {["fruit", "vegetable"].map((keyword) => (
            <BatchesTable
              key={keyword}
              batchesNames={batchesNames.filter((name) =>
                name.includes(keyword)
              )}
              viewMode="narrow"
              tableName={keyword}
            />
          ))}
        </div>
      ) : (
        <BatchesTable batchesNames={batchesNames} viewMode="wide" />
      )}
    </div>
  ) : (
    <div></div>
  );
}
解决方案

核心问题是mode切换后,旧的batchesNames未被清空就传给BatchesTable,导致表格用旧批次名+新mode发起无效请求。以下是几种可行的修复方案:

方案一:添加加载状态,仅在新数据就绪后渲染表格

修改BatchesPage组件,新增加载状态,在mode切换时立即清空旧数据,直到新批次列表获取完成再渲染表格:

import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";

import BatchesTable from "./BatchesTable";
import { fetchJson } from "./model";

export default function BatchesPage() {
  const [batchesNames, setBatchesNames] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const { mode } = useParams();
  const getBatchesNamesCmd = `/api/${mode}/list_batches`;

  useEffect(() => {
    // mode切换时立即重置状态
    setIsLoading(true);
    setBatchesNames([]);
    
    fetchJson(getBatchesNamesCmd)
      .then((jsonData) => {
        setBatchesNames(jsonData.sort().reverse());
      })
      .catch((error) => {
        console.error(`error occure: ${error}`);
      })
      .finally(() => {
        setIsLoading(false);
      });
  }, [getBatchesNamesCmd]);

  // 加载中时不渲染表格,避免旧数据触发无效请求
  if (isLoading) {
    return <div>加载中...</div>;
  }

  return (
    <div>
      {mode === "automatic" ? (
        <div className="horizontal-container">
          {["fruit", "vegetable"].map((keyword) => (
            <BatchesTable
              key={keyword}
              batchesNames={batchesNames.filter((name) =>
                name.includes(keyword)
              )}
              viewMode="narrow"
              tableName={keyword}
            />
          ))}
        </div>
      ) : (
        <BatchesTable batchesNames={batchesNames} viewMode="wide" />
      )}
    </div>
  );
}

方案二:在表格组件中绑定mode依赖,过滤无效批次

如果不想修改页面加载状态,可以在BatchesTable组件中接收mode参数,监听mode变化并清空自身数据,确保仅处理当前mode对应的批次:

修改BatchesTable组件

import React, { useEffect, useState } from "react";

export default function BatchesTable({ batchesNames, mode, ...rest }) {
  const [currentBatches, setCurrentBatches] = useState([]);

  useEffect(() => {
    // mode变化时清空当前批次,等待新的有效数据
    setCurrentBatches([]);
    setCurrentBatches(batchesNames);
  }, [mode, batchesNames]);

  // 无有效批次时不渲染表格内容
  if (!currentBatches.length) {
    return <div></div>;
  }

  // 原表格渲染逻辑...
}

在BatchesPage中传递mode参数

// automatic分支
<BatchesTable
  key={keyword}
  mode={mode}
  batchesNames={batchesNames.filter((name) => name.includes(keyword))}
  viewMode="narrow"
  tableName={keyword}
/>

// manual分支
<BatchesTable 
  mode={mode}
  batchesNames={batchesNames} 
  viewMode="wide" 
/>

方案三:使用AbortController取消旧请求(可选)

如果mode切换时旧的list_batches请求仍在进行,可以用AbortController取消它,避免无效的状态更新:

useEffect(() => {
  const controller = new AbortController();
  const signal = controller.signal;

  setIsLoading(true);
  setBatchesNames([]);

  fetchJson(getBatchesNamesCmd, { signal })
    .then((jsonData) => {
      setBatchesNames(jsonData.sort().reverse());
    })
    .catch((error) => {
      // 忽略请求取消的错误
      if (error.name !== 'AbortError') {
        console.error(`error occure: ${error}`);
      }
    })
    .finally(() => {
      setIsLoading(false);
    });

  // 组件卸载或依赖变化时取消请求
  return () => controller.abort();
}, [getBatchesNamesCmd]);

需要同步修改fetchJson函数支持signal参数:

export async function fetchJson(url, options = {}) {
  const response = await fetch(url, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return await response.json();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 09:37:03