解决React中useEffect重复执行的问题

霞舞

霞舞

2025-10-13

523人浏览

原创

解决react中useeffect重复执行的问题

React开发者经常遇到useEffect钩子意外执行两次的情况,尤其是在开发模式下。本文将深入探讨useEffect重复执行的原因,并提供有效的解决方案,确保你的副作用函数按预期运行,同时优化加载状态的管理,避免不必要的数据库操作。

为什么useEffect会执行两次?

在React 18及更高版本中,严格模式(Strict Mode)默认启用,尤其是在开发环境下。严格模式旨在帮助开发者尽早发现潜在问题,它会故意触发某些副作用函数两次,以暴露不纯的渲染和潜在的错误。这通常是useEffect重复执行的罪魁祸首。

如何解决useEffect重复执行问题?

虽然严格模式是导致useEffect重复执行的主要原因,但我们通常不建议直接禁用它,因为它有助于发现潜在的bug。相反,我们应该修改我们的代码,使其能够正确处理重复执行的情况。以下是一些有效的策略:

1. 幂等性操作

确保你的副作用函数是幂等的。这意味着无论函数执行多少次,其结果都应该相同。例如,如果你的useEffect用于向数据库发送请求,确保你的API能够处理重复请求,或者在客户端实现去重逻辑。

2. 利用useRef存储状态

如果useEffect中的副作用是基于某个状态的,你可以使用useRef来存储该状态,并在每次执行副作用之前检查该状态是否已经更新。useRef创建的是一个可以在组件的整个生命周期中保持不变的变量,且修改useRef不会触发组件重新渲染。

3. 移除或调整严格模式

如果确认严格模式是导致问题的唯一原因,并且无法修改副作用函数以适应重复执行,可以考虑暂时移除或调整严格模式的范围。但请记住,这可能会隐藏潜在的问题,因此应谨慎使用。

度加AI
度加AI

度加AI官网入口,百度官方 AIGC 创作平台,支持 AI 成片、AI 生文、数字人、声音克隆、配音字幕与智能剪辑等在线创作能力。

下载

4. 优化依赖项

检查useEffect的依赖项数组。确保只包含真正需要监听的状态或props。不必要的依赖项会导致useEffect在不必要的时候执行。

示例代码分析与改进

以下面的代码为例,展示如何解决useEffect重复执行以及加载状态管理的问题:

import { type AppType } from 'next/app';
import { api } from '~/utils/api';
import '~/styles/globals.css';
import Nav from '~/components/Nav';
import { useEffect, useState } from 'react';

const MyApp: AppType = ({ Component, pageProps }) => {
  const [showCart, setShowCart] = useState(false);
  const [loading, setLoading] = useState(false);

  const createSession = api.user.createSession.useMutation();

  const generateId = async (): Promise<string undefined> => {
    const res = await createSession.mutateAsync();
    if (res.response) {
      return res.response.id;
    } else if (res.error) {
      return res.error;
    }
  };

  const setSessionId = async () => {
    const tmp: string | undefined = await generateId();
    if (tmp) document.cookie = `sessionId=${tmp}`;
    setLoading(false);
  };

  useEffect(() => {
    if (!loading) {
      setLoading(true);
      const cookieString: string = document.cookie;

      const cookies: string[] = cookieString.split(';') || [];

      let sessionId: string | null = null;

      for (let i = 0; i 
      <nav showcart="{showCart}" setshowcart="{setShowCart}"></nav><h1>{loading && 'LOADING'}</h1>
      <component></component>>
  );
};

export default api.withTRPC(MyApp);</string>

问题分析:

  1. useEffect依赖项为空,导致组件每次渲染都会执行。
  2. loading状态的设置和使用不一致,导致加载状态显示不正确。

改进后的代码:

import { type AppType } from 'next/app';
import { api } from '~/utils/api';
import '~/styles/globals.css';
import Nav from '~/components/Nav';
import { useEffect, useState } from 'react';

const MyApp: AppType = ({ Component, pageProps }) => {
  const [showCart, setShowCart] = useState(false);
  const [loading, setLoading] = useState(true); // 初始化loading为true

  const createSession = api.user.createSession.useMutation();

  const generateId = async (): Promise<string undefined> => {
    const res = await createSession.mutateAsync();
    if (res.response) {
      return res.response.id;
    } else if (res.error) {
      return res.error;
    }
  };

  const setSessionId = async () => {
    const tmp: string | undefined = await generateId();
    if (tmp) document.cookie = `sessionId=${tmp}`;
    setLoading(false);
  };

  useEffect(() => {
    const getSessionId = () => {
      const cookieString: string = document.cookie;
      const cookies: string[] = cookieString.split(';') || [];

      let sessionId: string | null = null;

      for (let i = 0; i 
      <nav showcart="{showCart}" setshowcart="{setShowCart}"></nav>
      {loading && <h1>LOADING</h1>} {/* 仅当loading为true时显示 */}
      <component></component>>
  );
};

export default api.withTRPC(MyApp);</string>

改进说明:

  1. 初始化 loading 状态: 将 loading 的初始状态设置为 true,确保在组件挂载时显示加载状态。
  2. 移除不必要的 loading 判断: 在 useEffect 内部移除了 if (!loading) 的判断,因为初始时 loading 已经是 true,并且在获取或创建 sessionId 后会设置为 false。
  3. useEffect 依赖项: 保持依赖项为空数组 [],确保 useEffect 只在组件挂载时执行一次。

总结

useEffect重复执行是React开发中常见的问题,但通过理解其原因并采取适当的措施,可以有效地解决这个问题。关键在于理解严格模式的作用,确保副作用函数的幂等性,并优化依赖项。同时,正确管理加载状态,可以提升用户体验。通过本文提供的策略和示例代码,相信你能更好地掌握useEffect的使用,避免重复执行带来的问题。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

2023.08.22

1020

3

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

2023.06.29

1283

3

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2023.08.14

2787

10

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

2023.08.31

1237

3

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.05

525

5

vb中怎么连接access数据库
vb中怎么连接access数据库

vb中连接access数据库的步骤包括引用必要的命名空间、创建连接字符串、创建连接对象、打开连接、执行SQL语句和关闭连接。本专题为大家提供连接access数据库相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.09

1291

5

数据库对象名无效怎么解决
数据库对象名无效怎么解决

数据库对象名无效解决办法:1、检查使用的对象名是否正确,确保没有拼写错误;2、检查数据库中是否已存在具有相同名称的对象,如果是,请更改对象名为一个不同的名称,然后重新创建;3、确保在连接数据库时使用了正确的用户名、密码和数据库名称;4、尝试重启数据库服务,然后再次尝试创建或使用对象;5、尝试更新驱动程序,然后再次尝试创建或使用对象。

2023.10.16

1289

4

vb连接access数据库的方法
vb连接access数据库的方法

vb连接access数据库方法:1、使用ADO连接,首先导入System.Data.OleDb模块,然后定义一个连接字符串,接着创建一个OleDbConnection对象并使用Open() 方法打开连接;2、使用DAO连接,首先导入 Microsoft.Jet.OLEDB模块,然后定义一个连接字符串,接着创建一个JetConnection对象并使用Open()方法打开连接即可。

2023.10.16

2328

3

vb连接数据库的方法
vb连接数据库的方法

vb连接数据库的方法有使用ADO对象库、使用OLEDB数据提供程序、使用ODBC数据源等。详细介绍:1、使用ADO对象库方法,ADO是一种用于访问数据库的COM组件,可以通过ADO连接数据库并执行SQL语句。可以使用ADODB.Connection对象来建立与数据库的连接,然后使用ADODB.Recordset对象来执行查询和操作数据;2、使用OLEDB数据提供程序方法等等。

2023.10.19

1166

3

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 1.3万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 5万人学习

CSS教程
CSS教程

共754课时 | 84.5万人学习