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

React中useEffect重复触发API调用问题求助

解决API重复调用问题的方案

问题根源

你当前的useEffect没有设置依赖数组,这意味着组件每一次重渲染(任何状态、上下文变化都会触发重渲染)都会执行这个effect里的逻辑,自然会导致API被多次调用。另外,即使加上依赖,快速切换分支/日期也可能触发连续请求,所以还要做防抖处理。

具体修复步骤

1. 给useEffect添加精准的依赖数组

把effect的依赖数组设置为[cities, selectedBranchIndex, selectedDate, branch],这样只有当这些关联值发生变化时,才会执行温度获取逻辑,避免无意义的重复执行。

2. 增加防抖逻辑

用useRef存储防抖定时器,每次触发请求前清除之前的定时器,延迟一段时间(比如500ms)再发起API调用,避免用户快速操作导致的连续请求。

3. 修复cities的校验逻辑

你代码里cities !== "undefined"是错误的,因为cities是上下文提供的对象,不是字符串,应该改成cities?.cities来判断是否有有效数据。

4. 可选:添加请求取消机制

用AbortController在组件重渲染或依赖变化时取消未完成的请求,避免旧请求返回覆盖最新结果。

修改后的完整代码

import Calendar from "../components/Calendar";
import Languages from "../components/Languages/Languages.json";
import React, { useContext, useEffect, useRef, useState } from "react";
import { ContextCities } from "../Contexts/ContextCities";
import { ContextLanguages } from "../Contexts/ContextLanguages";

import { loadBranches } from "../utils/branchesHandelApi";
import { loadWeather } from "../utils/weatherHandelApi";

export default function Transactions() {
    const { cities } = useContext(ContextCities);
    const { selectedLanguage } = useContext(ContextLanguages);

    const [branch, setBranch] = useState([]);
    const [selectedBranchIndex, setSelectedBranchIndex] = useState(0);
    const [selectedDate, setSelectedDate] = useState();
    const [selectedDateData, setSelectedDateData] = useState();
    const [weather, setWeather] = useState(0);

    // 防抖定时器Ref
    const debounceTimer = useRef(null);
    // 请求取消控制器Ref
    const abortControllerRef = useRef(null);

    const handleSubmitTransaction = async (e) => {
        e.preventDefault();
        // saveTransactions(transaction);
    };

    useEffect(() => {
        loadBranches(setBranch);
    }, []);

    useEffect(() => {
        // 初始挂载或参数无效时重置温度
        if (selectedBranchIndex === 0 || !selectedDate || !cities?.cities) {
            setWeather(0);
            return;
        }

        // 清除之前的防抖定时器
        if (debounceTimer.current) {
            clearTimeout(debounceTimer.current);
        }

        // 取消之前未完成的请求
        if (abortControllerRef.current) {
            abortControllerRef.current.abort();
        }

        debounceTimer.current = setTimeout(async () => {
            try {
                abortControllerRef.current = new AbortController();
                const targetCity = branch[selectedBranchIndex - 1]?.city;
                if (!targetCity) return;

                const station = cities.cities.find(itm => itm.Name === targetCity)?.meteorological_station;
                if (!station?.id) return;

                console.log("station_id", station.id);
                // 假设loadWeather支持传入signal参数做请求取消
                await loadWeather(station.id, selectedDate, setWeather, abortControllerRef.current.signal);
            } catch (err) {
                // 忽略请求取消的错误
                if (err.name !== "AbortError") {
                    console.error("获取天气失败:", err);
                    setWeather(0);
                }
            }
        }, 500); // 500ms防抖延迟,可根据需求调整

        // 清理函数:组件卸载或依赖变化时清除定时器和取消请求
        return () => {
            clearTimeout(debounceTimer.current);
            if (abortControllerRef.current) {
                abortControllerRef.current.abort();
            }
        };
    }, [cities, selectedBranchIndex, selectedDate, branch]); // 核心:添加依赖数组

    return (
        <>
            <h1>
                <span style={{ fontSize: "2rem", fontFamily: "Noto Color Emoji" }}>
                    {"💳"}
                </span>{" "}
                {Languages.transactions[selectedLanguage]}
            </h1>
            <form
                autoComplete="off"
                className="App"
                id="form"
                onSubmit={(e) => handleSubmitTransaction(e)}
            >
                <fieldset>
                    <legend>
                        <h2>{Languages.manual_update[selectedLanguage]}</h2>
                    </legend>
                    <h2>{Languages.branches[selectedLanguage]}</h2>
                    <h3>{Languages.name[selectedLanguage]}:</h3>{" "}
                    <select
                        defaultValue=""
                        name="branch"
                        required
                        onChange={(e) => setSelectedBranchIndex(e.target.selectedIndex)}
                    >
                        <option hidden value="">
                            {Languages.name[selectedLanguage]}
                        </option>
                        {branch.map((itm, index) => (
                            <option key={index}>{itm.name}</option>
                        ))}
                    </select>
                    &emsp;
                    <h3>{Languages.city[selectedLanguage]}:</h3>{" "}
                    <input
                        disabled="disabled"
                        readOnly
                        value={
                            selectedBranchIndex === 0
                                ? ""
                                : branch[selectedBranchIndex - 1]?.city || ""
                        }
                    />
                    <br />
                    <br />
                    <h2>{Languages.date[selectedLanguage]}</h2>
                    <Calendar
                        setSelectedDate={setSelectedDate}
                        setSelectedDateData={setSelectedDateData}
                    />
                    <br />
                    <br />
                    <h2>{Languages.weather[selectedLanguage]}</h2>
                    <h3>{Languages.meteorological_station_id[selectedLanguage]}:</h3>{" "}
                    <input
                        readOnly
                        disabled="disabled"
                        value={
                            selectedBranchIndex === 0 || !cities?.cities
                                ? ""
                                : cities.cities.find(
                                        (itm) =>
                                            itm.Name === branch[selectedBranchIndex - 1]?.city
                                  )?.meteorological_station.id || ""
                        }
                    />
                    &emsp;
                    <h3>
                        {Languages.meteorological_station_location[selectedLanguage]}:
                    </h3>{" "}
                    <input
                        readOnly
                        disabled="disabled"
                        value={
                            selectedBranchIndex === 0 || !cities?.cities
                                ? ""
                                : cities.cities.find(
                                        (itm) =>
                                            itm.Name === branch[selectedBranchIndex - 1]?.city
                                  )?.meteorological_station.name || ""
                        }
                    />
                    &emsp;
                    <h3>{Languages.temperature[selectedLanguage]}:</h3>{" "}
                    <input readOnly disabled="disabled" value={weather} />
                    {" ℃"}
                </fieldset>
            </form>
        </>
    );
}

额外说明

  • 防抖延迟时间可以根据实际需求调整,比如300ms-1000ms都可以。
  • 如果你的loadWeather函数不支持AbortSignal,可以去掉取消请求的部分,只保留防抖逻辑也能解决大部分重复调用问题。
  • 代码中添加了可选链?.,避免因为branch或cities数据未加载完成导致的报错。

内容的提问来源于stack exchange,提问作者Y.S.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 13:00:46