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

为何使用useEffect在页面渲染时调用的函数会执行两次?

解决React中handleInitiateSearch函数重复执行两次的问题

问题分析

你遇到的情况大概率是React 18严格模式在开发环境下的刻意行为——严格模式会双重执行useEffect、组件初始化逻辑等,目的是提前检测潜在的副作用问题,但生产环境不会触发这个机制。如果禁用严格模式后仍无效,可能是组件被多次挂载,或者存在其他触发重渲染的逻辑。

解决方案

1. 正确关闭React严格模式

检查你的根组件文件(比如layout.tsx或_app.tsx),移除<React.StrictMode>标签。开发环境下关闭后,useEffect就只会执行一次:

// layout.tsx(Next.js 13+)示例
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

// 移除<React.StrictMode>包裹
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  )
}

2. 用状态标记控制执行次数

如果需要保留严格模式,或者要在特定重渲染场景下重新执行函数,可以添加状态标记来限制执行,同时支持手动触发重新运行:

"use client";
import { useEffect, useState } from "react";

export default function Home() {
  const [loading, setLoading] = useState(false);
  const [roads, setRoads] = useState([]);
  const [error, setError] = useState(false);
  const [errorMessage, setErrorMessage] = useState("");
  // 标记是否已执行过初始搜索
  const [hasRunInitialSearch, setHasRunInitialSearch] = useState(false);
  // 触发重新执行的开关,每次递增数值来触发useEffect
  const [refreshFlag, setRefreshFlag] = useState(0);

  useEffect(() => {
    // 首次加载或手动触发刷新时执行
    if (!hasRunInitialSearch || refreshFlag > 0) {
      handleInitiateSearch();
      setHasRunInitialSearch(true);
    }
  }, [refreshFlag]);

  async function handleInitiateSearch() {
    setLoading(true);
    try {
      const roadsResponse = await fetch(`/api?road=all`);
      const roadsData = await roadsResponse.json();
      console.log("Fetch response:", roadsData);
      setRoads(roadsData.roads);
      if (roadsResponse.status === 201) {
        setError(true);
        setErrorMessage(roadsResponse.statusText);
      }
    } finally {
      setLoading(false);
    }
  }

  // 手动触发重新搜索的方法
  const triggerRefresh = () => {
    setHasRunInitialSearch(false);
    setRefreshFlag(prev => prev + 1);
  };

  return (
    <div>
      {loading && <p>加载中...</p>}
      <button onClick={triggerRefresh}>重新获取数据</button>
      {/* 组件其他内容 */}
    </div>
  );
}

3. 让请求幂等/取消重复请求(解决GitLab流水线问题)

如果重复请求破坏了GitLab流水线,优先让后端API支持幂等性(比如用唯一请求ID,确保重复请求不会触发重复流水线任务)。前端也可以用AbortController取消未完成的重复请求:

"use client";
import { useEffect, useState } from "react";

export default function Home() {
  // ... 其他状态
  const [currentAbortController, setCurrentAbortController] = useState(null);

  useEffect(() => {
    handleInitiateSearch();
  }, []);

  async function handleInitiateSearch() {
    setLoading(true);
    // 创建新的控制器,取消之前未完成的请求
    const controller = new AbortController();
    if (currentAbortController) {
      currentAbortController.abort();
    }
    setCurrentAbortController(controller);

    try {
      const roadsResponse = await fetch(`/api?road=all`, {
        signal: controller.signal
      });
      const roadsData = await roadsResponse.json();
      console.log("Fetch response:", roadsData);
      setRoads(roadsData.roads);
      if (roadsResponse.status === 201) {
        setError(true);
        setErrorMessage(roadsResponse.statusText);
      }
    } catch (error) {
      // 忽略请求被取消的错误
      if (error.name !== 'AbortError') {
        setError(true);
        setErrorMessage(error.message);
      }
    } finally {
      setLoading(false);
      setCurrentAbortController(null);
    }
  }

  // ... 组件渲染内容
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 00:58:24