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

React 18中useEffect首次页面加载时执行两次的原因排查

问题根因

这是React 18开发环境的默认行为,不是代码bug。
React 18默认在入口文件开启了<React.StrictMode>严格模式,开发环境下组件首次挂载时,会对依赖数组为空的useEffect执行「挂载→卸载→重新挂载」的模拟流程,用来提前暴露代码中未正确清理的副作用问题(比如未取消的请求、未清除的定时器、未解绑的事件监听),因此你会看到effect执行了两次。这个双调用逻辑仅在开发环境生效,生产环境打包后不会出现。

可选解决方案

推荐方案:给请求增加取消逻辑(符合React设计规范)

这是官方推荐的写法,不仅能解决严格模式下的重复请求问题,还能避免组件快速卸载/重挂载时的请求竞态问题。修改后的代码如下:

import axios from "axios";
import React, { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { useParams } from "react-router-dom";
import { BASE_API_URL } from "../Variables/Urls";

const SingleRecipeView = () => {
  const { slug } = useParams();
  const [recipe, setRecipe] = useState({});
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const abortController = new AbortController();
    const fetchRecipe = async () => {
      try {
        setLoading(true);
        const res = await axios.get(`${BASE_API_URL}/recipe/get/${slug}`, {
          signal: abortController.signal
        });
        console.log(res.data);
        setRecipe(res.data);
        setLoading(false);
      } catch (err) {
        // 主动取消的请求不需要抛错提示
        if (!axios.isCancel(err)) {
          console.log(err);
          setLoading(false);
          toast.error("Error loading recipe");
        }
      }
    };
    fetchRecipe();
    // 组件卸载时终止未完成的请求
    return () => abortController.abort();
    // 注意:slug是effect内用到的外部变量,需要加入依赖数组
  }, [slug]);

  // 其余组件渲染逻辑
}

简易方案:加请求标记控制只执行一次

如果只是想快速避免重复请求,可以用useRef存一个标记位,判断是否已经发起过请求:

import React, { useEffect, useState, useRef } from "react";
// 其余导入逻辑不变

const SingleRecipeView = () => {
  const { slug } = useParams();
  const [recipe, setRecipe] = useState({});
  const [loading, setLoading] = useState(true);
  const hasFetched = useRef(false);

  const fetchRecipe = async () => {
    // 原有请求逻辑保持不变
  };

  useEffect(() => {
    if (hasFetched.current) return;
    hasFetched.current = true;
    fetchRecipe();
  }, [slug]);

  // 其余组件渲染逻辑
}

注意:这个写法无法解决路由参数快速变化时的请求竞态问题,只适合简单场景使用。

不推荐方案:关闭严格模式

可以直接删除入口文件(通常是src/main.jsx/src/index.js)中包裹根组件的<React.StrictMode>标签,关闭后开发环境也不会出现双调用。但严格模式可以提前帮你发现很多潜在的代码问题,非必要不建议关闭。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 01:09:25