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

React 18严格模式下App重复执行触发多次服务端调用问题

问题背景

测试React函数调用逻辑时,发现在严格模式下App.js内的函数会被多次调用,导致文件重复执行、触发多次服务端请求,当前使用的React版本为18。

相关代码

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

reportWebVitals();

App.js

import React from "react";
import "./App.css";
import Auth from "./Auth";
import { Route, Routes, BrowserRouter } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Auth />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

Auth.js

import React, { useState, useRef } from "react";
import "./App.css";
import CheckCurrentUser from "./CheckCurrentUser";

let counter = 0;
let currentState;

function Auth() {
  const [loginState, setLoginState] = useState(false);
  const CurrentAuthenticatedUser = useRef(undefined);
  const loading = useRef(false);

  async function checkLoginState() {
    if (!loading.current) {
      console.log(loading.current);
      loading.current = true;
      console.log(loading.current);
      counter += 1;
      console.log("before the await function : " + counter);
      if (!CurrentAuthenticatedUser.current) {
        console.log("counter value inside the if is :" + counter);
        console.log(
          "current authenticated user is :" + CurrentAuthenticatedUser.current
        );
        CurrentAuthenticatedUser.current = await CheckCurrentUser();
        console.log(loading.current);
        setLoginState(CurrentAuthenticatedUser.current);
        loading.current = false;
      }
    }
    console.log("after the await function : " + counter);
  }

  function loadingCheckLoginState() {
    checkLoginState();
    loading.current = true;
  }

  console.log("executing before");
  currentState = loading.current
    ? console.log("loading is on")
    : loadingCheckLoginState();
  console.log("executing after");

  return <h1>Open Points</h1>;
}

export default Auth;

CheckCurrentUser.js

function CheckCurrentUser() {
  try {
    console.log("inside check current user document try block");
    fetch(`https://jsonplaceholder.typicode.com/posts`).then((response) =>
      console.log(response)
    );
    return true;
  } catch (error) {
    console.log("no logged in user - inside catch block");
    return null;
  }
}

export default CheckCurrentUser;

核心疑问

  1. 为什么App.js会被执行两次?
  2. 为何组件第二次执行时,会忽略前一次执行生成的状态结果?

问题解答

1. App.js执行两次的核心原因

这是React 18严格模式(React.StrictMode)下仅开发环境生效的默认检测行为,生产环境打包后不会出现。
React 18为了提前揪出代码里不符合并发渲染规则的问题,会在开发环境自动执行双挂载流程:组件首次挂载完成后立刻触发一次卸载,再重新挂载一次组件。整个过程不会真的销毁重建DOM,专门用来检测组件有没有副作用没清理、逻辑不幂等(执行多次和执行一次结果不一致)这类隐藏bug。
你在index.js里把根组件包裹在了<React.StrictMode>标签中,所以App组件以及它所有的子组件都会触发这个双调用检测。

2. 第二次执行读不到前一次状态的原因

要分两类存储的变量来看:

  • React托管的状态:用useState、useRef创建的状态和ref值,都是和组件实例绑定的。严格模式下第一次挂载后卸载组件时,React会直接销毁这一轮实例对应的所有状态、ref值,第二次挂载是全新的组件实例,所有hook都会重新走初始化流程,自然读不到上一轮存的值。
  • 组件外部声明的普通全局变量:比如你代码里写的counter、currentState,这类变量不归React管,两次组件执行操作的都是内存里同一个变量,所以你会看到counter的值一直在累加。你写的loading ref拦截逻辑之所以没挡住重复请求,就是因为第一次挂载给ref设的true值,在组件卸载时被重置成了初始的false,第二次挂载判断时还是会进请求逻辑。

额外注意:你现在把请求逻辑直接写在组件渲染函数顶层执行,本身就不符合React的开发规范。副作用类逻辑(比如接口请求、事件绑定)必须放到useEffect里执行,同时写好卸载时的清理逻辑,这样不管是严格模式双调用,还是后续并发渲染的调度,都不会出现重复触发请求的问题。另外你的CheckCurrentUser函数里fetch没有加await,函数返回true的时候请求还没完成,返回值和实际请求结果完全脱节,这个逻辑也需要修正。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 09:51:17