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

React子组件useEffect中API调用刷新时触发两次问题排查

问题分析与解决方案

问题根源

当前API调用触发两次的原因主要有两个:

  1. 刷新按钮的两次setMyTabs调用,导致子组件先被卸载再重新挂载,触发了挂载时的useEffect执行。
  2. 若处于React开发环境的严格模式,框架会额外重复执行一次useEffect用于检测副作用,进一步加剧了重复调用的问题。

原逻辑依赖组件挂载触发API,这种方式在组件频繁卸载/挂载时会不符合需求,我们需要改为主动控制API的触发时机。


推荐解决方案:用刷新标识触发API

不需要通过卸载/挂载组件来触发刷新,而是给子组件传递一个刷新标识,当标识变化时主动触发API调用。

父组件修改

import { useState } from 'react';

// 新增刷新标识状态
const [refreshKey, setRefreshKey] = useState(0);
const defaultTabs = {
    section1: false,
    section2: false,
    section3: false
};

// 修改刷新按钮的点击逻辑
const handleRefresh = () => {
    // 只需更新刷新标识,无需切换tab的显示状态
    setRefreshKey(prev => prev + 1);
};

// 父组件JSX中传递refreshKey给子组件
{myTabs.section2 && (
    <div>
        <MySection2Component 
            hasLoaded={hasLoaded} 
            updateDateTime={updateDateTime} 
            refreshKey={refreshKey} 
            {...props} 
        />
    </div>
)}

子组件修改

import { useState, useEffect } from 'react';

const MySection2Component = (props) => {
    const [mySection2Obj, setMySection2Obj] = useState({});
    const [isChildLoading, setIsChildLoading] = useState(true); // 补全加载状态的定义

    // 依赖refreshKey触发API,首次加载和刷新时都会执行
    useEffect(() => {
        makeApiCall();
    }, [props.refreshKey]);

    useEffect(() => {
        props.hasLoaded(!isChildLoading);
    }, [isChildLoading, props.hasLoaded]); // 添加props依赖避免React警告

    async function makeApiCall() {
        setIsChildLoading(true);
        try {
            const urlMain = myUrl + "/section2/endpoint";
            const result = await ReactCall.post(urlMain);
            responseHandler(result);
            
            if (result.data) {
                const mySection2Response = result?.data?.result?.sections.find(item => item.id === 'section-2');
                setMySection2Obj({
                    attr1: mySection2Response?.fields?.attr1,
                    attr2: mySection2Response?.fields?.attr2
                });
                props.updateDateTime(`Refreshed at ${result?.data?.refreshDateTime}`);
            }
        } catch (err) {
            // 可添加错误处理逻辑
            console.error('API调用失败:', err);
        } finally {
            setIsChildLoading(false); // 无论成功失败都结束加载状态
        }
    }

    return <div>{/* 子组件渲染内容 */}</div>;
};

备选方案:优化父组件刷新逻辑

如果一定要保留tab切换的逻辑,可修改刷新时的状态更新,跳过中间的卸载步骤:

// 刷新按钮点击时直接设置为选中当前tab,避免子组件卸载
setMyTabs({...defaultTabs, [selectedTabId]: true});

但这种方式只能在组件首次挂载时触发一次API,刷新时仍需结合上述方案的refreshKey来主动触发API调用。


额外注意事项

  • 开发环境下React严格模式的重复useEffect是调试行为,生产环境不会出现,无需刻意规避。
  • 给API调用添加try/catch,避免请求失败导致加载状态一直处于true。
  • 确保所有useEffect的依赖项完整,避免React的依赖警告。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 19:07:09