[译] 为何 React 18 中 useEffect 会运行两次?

React18发布后,useEffect在开发模式下默认可能会运行两次,仅限于strict mode。这可能导致应用行为变化,但只影响开发环境。为解决此问题,可以暂时禁用strict mode,如在Next.js中设置reactStrictMode: false,或者在create-react-app应用中移除StrictMode组件。这是一个临时解决方案,开发者应修复任何由此产生的问题。

f634db20a738aed9a9e1ba007f1c74b2.png

原文:https://flaviocopes.com/react-useeffect-two-times/

在 2022 年 3 月发布的 React 18 的发布公告中,数量可观的新特性扑面而来。而对 useEffect() 的默认行为的改变,可能就此被淹没了。

如果你的应用在更新到 React 18 之后行为迥异,或许正是因为 useEffect() 默认变为被运行 2 次了

虽然这种情况只发生在 development mode 中,但无疑每位开发者都会遇到。

另一个限制条件是 只在 strict mode 发生,但这同样是用 create-react-app 或 Next.js 所构建应用的默认选项。

事情就是这么个事情,所以不用紧张是不是代码出了什么问题 -- React 的现状如此。

唯一避免这种行为的方法就是 禁用 strict mode;鉴于严格模式的重要性,这毕竟是个你能修复这种改变引入的任何问题之前临时的变通之策。

在 Next.js 中,可以在 next.config.js 文件里增加这个选项:

reactStrictMode: false

create-react-app 创建的应用里,可以把 index.js 文件里的 StrictMode 高阶组件去除:

比如从这样:

import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

import App from './App';

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

改为:

import React from 'react';
import { createRoot } from 'react-dom/client';

import App from './App';

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);

root.render(
  <App />
);

d2243f2bfca57b7ece2e5dd974eaf6f2.png6807f708cc0516339c084299f08261de.png

<think>我们根据用户的问题,需要详细讲解React函数组件中useEffect Hook的使用方法和不同用法。结合提供的引用内容,我们可以将回答分为几个部分:基础用法、高级用法(包括清理副作用和异步操作)、特殊场景处理以及执行机制总结。最后生成相关问题。 注意:引用内容中提到的要点需要自然地融入回答中,并在末尾添加引用标识。 结构安排: 1. 基础用法:包括无依赖项、空依赖数组(模拟挂载)、有依赖数组的情况。 2. 高级用法:清理副作用(cleanup函数)、异步数据获取、DOM操作。 3. 特殊场景处理:避免无限循环、性能优化、组合依赖项。 4. 执行机制总结:用表格形式展示不同场景的执行时机。 5. 生成相关问题。 在回答中,我们使用引用标记[^1][^2][^3][^4]来标注引用的内容。 注意:用户上一次的问题是关于useEffect的用法详解,我们这次需要参考站内引用,但也要结合之前的回答,避免重复,同时补充新的内容(如引用中提到的自定义Hook实现、多个useEffect的清理等)。 我们开始组织回答:</think>### React 函数组件中 `useEffect` 的用法详解 #### 一、基础用法 1. **无依赖项执行(每次渲染后运行)** ```jsx useEffect(() => { console.log("组件每次渲染后执行"); }); ``` > 相当于类组件的 `componentDidMount` + `componentDidUpdate`,适用于需持续跟踪状态变化的场景[^1][^3]。 2. **模拟挂载(仅首次渲染执行)** ```jsx useEffect(() => { console.log("仅在组件挂载时执行一次"); // 初始化操作(如API请求、订阅) }, []); // 空依赖数组 ``` > 相当于 `componentDidMount`,常用于初始化操作[^1][^2]。 3. **依赖项触发(特定值变化时执行)** ```jsx const [count, setCount] = useState(0); useEffect(() => { console.log("count变化时执行:", count); }, [count]); // 仅在count变化时触发 ``` > 精确控制副作用执行时机,避免不必要的重复操作[^1][^3]。 --- #### 二、高级用法 1. **清理副作用(cleanup函数)** ```jsx useEffect(() => { const timer = setInterval(() => console.log("定时器运行"), 1000); return () => clearInterval(timer); // 组件卸载时清理 }, []); ``` > 清理函数在组件卸载或依赖项变化前执行,相当于 `componentWillUnmount`[^1][^4]。 2. **异步数据获取(防内存泄漏)** ```jsx useEffect(() => { let isMounted = true; const fetchData = async () => { const data = await fetch('/api/data'); if (isMounted) setData(data); // 避免更新已卸载组件 }; fetchData(); return () => { isMounted = false }; // 清理函数取消异步操作 }, []); ``` > 通过标志位解决组件卸载后更新状态的时序问题[^1][^3]。 3. **多副作用独立管理** ```jsx useEffect(() => { /* 副作用A */; return () => cleanupA() }, [depA]); useEffect(() => { /* 副作用B */; return () => cleanupB() }, [depB]); ``` > 每个 `useEffect` 可独立声明清理函数,避免逻辑耦合[^4]。 --- #### 三、关键注意事项 1. **避免无限循环** ```jsx // 错误:未过滤依赖项变化 useEffect(() => setCount(count + 1), [count]); // 正确:添加条件判断 useEffect(() => { if (needsUpdate) setCount(c => c + 1); }, [needsUpdate]); ``` 2. **依赖项优化原则** - 包含所有外部变量(props/state) - 使用函数式更新避免直接依赖:`setCount(c => c + 1)` - 复杂对象需深度比较(如使用 `useDeepCompareEffect`) 3. **性能敏感操作** ```jsx useEffect(() => { // 对大数据集进行过滤计算 }, [props.filter]); // 仅当筛选条件变化时执行 ``` --- ### 执行机制总结 | **场景** | **依赖数组** | **执行时机** | **清理触发时机** | |------------------------|----------------|----------------------------------|-----------------------------| | 每次渲染后执行 | 不传 | 每次组件更新 | 下次effect执行前 | | 仅挂载时执行 | `[]` | 组件挂载时 | 组件卸载时 | | 特定值变化时执行 | `[dep1, dep2]` | 依赖项变化时 | 依赖变化或卸载时 | | 卸载时清理 | 返回清理函数 | 不单独执行 | 组件卸载或依赖变化前 | > **核心原则**:副作用操作应与渲染流程解耦,避免阻塞渲染[^1][^2]。 --- ### 相关问题 1. 如何避免 `useEffect` 捕获过时的闭包变量? 2. `useEffect` 和 `useLayoutEffect` 的核心区别是什么? 3. 为什么在 `useEffect` 中直接修改状态可能引发无限循环? 4. 如何用自定义 Hook 封装 `useEffect` 的副作用逻辑? 5. 依赖项数组中包含对象/函数时需要注意什么? [^1]: ReactuseEffect Hook是一个非常有用的工具,可以帮助我们处理副作用操作[^1]。 [^2]: useEffect允许在函数组件中执行副作用操作,包括数据获取、手动 DOM 操作等[^2]。 [^3]: `useEffect`接受两个参数:副作用函数和可选的依赖项数组[^3]。 [^4]: 每个`useEffect`应独立定义自己的清理函数[^4]。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值