一、useEffect基础概念

1、什么是副作用(Side Effects)?
在React中,副作用是指那些与组件渲染结果无关的操作,例如:

  • 数据获取(API调用)
  • 手动修改DOM
  • 设置订阅或定时器
  • 记录日志

2、useEffect的基本语法

import { useEffect } from 'react';function MyComponent() {useEffect(() => {// 副作用逻辑在这里执行return () => {// 清理函数(可选)};}, [dependency1, dependency2]); // 依赖数组(可选)
}

二、useEffect的三种适用方式

1、每次渲染后都执行

useEffect(() => {// 每次组件渲染后都会执行console.log('组件已渲染或更新');
});

2、仅在挂载时执行一次

useEffect(() => {// 只在组件挂载时执行一次console.log('组件已挂载');return () => {// 清理函数,在组件卸载时执行console.log('组件即将卸载');};
}, []); // 空依赖数组

3、依赖特定值变化时执行

useEffect(() => {// 当 count 或 name 变化时执行console.log(`Count: ${count}, Name: ${name}`);return () => {// 清理上一次的 effectconsole.log('清理上一次的 effect');};
}, [count, name]); // 依赖数组

三、useEffect执行机制详解

请添加图片描述

四、常见使用场景

1、数据获取

import { useState, useEffect } from 'react';function UserProfile({ userId }) {const [user, setUser] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {// 重置状态setLoading(true);setError(null);const fetchUser = async () => {try {const response = await fetch(`/api/users/${userId}`);const userData = await response.json();setUser(userData);} catch (err) {setError(err.message);} finally {setLoading(false);}};fetchUser();// 不需要清理函数,因为 fetch 会自动取消}, [userId]); // 当 userId 变化时重新获取if (loading) return <div>加载中...</div>;if (error) return <div>错误: {error}</div>;return (<div><h1>{user.name}</h1><p>{user.email}</p></div>);
}

2、事件监听器

function WindowSizeTracker() {const [windowSize, setWindowSize] = useState({width: window.innerWidth,height: window.innerHeight});useEffect(() => {const handleResize = () => {setWindowSize({width: window.innerWidth,height: window.innerHeight});};// 添加事件监听window.addEventListener('resize', handleResize);// 清理函数:移除事件监听return () => {window.removeEventListener('resize', handleResize);};}, []); // 空数组表示只在挂载/卸载时执行return (<div>窗口尺寸: {windowSize.width} x {windowSize.height}</div>);
}

3、定时器

function Timer() {const [seconds, setSeconds] = useState(0);useEffect(() => {const intervalId = setInterval(() => {setSeconds(prevSeconds => prevSeconds + 1);}, 1000);// 清理函数:清除定时器return () => {clearInterval(intervalId);};}, []); // 空依赖数组,只在挂载时设置定时器return <div>已运行: {seconds}</div>;
}

4、手动操作DOM

function FocusInput() {const inputRef = useRef(null);useEffect(() => {// 组件挂载后自动聚焦输入框if (inputRef.current) {inputRef.current.focus();}}, []); // 空数组表示只在挂载时执行return <input ref={inputRef} placeholder="自动聚焦" />;
}

五、依赖数组的详细说明

1、依赖数组的规则

// ✅ 正确:包含所有依赖
useEffect(() => {document.title = `${title} - ${count} 次点击`;
}, [title, count]); // 所有依赖都声明// ❌ 错误:缺少依赖
useEffect(() => {document.title = `${title} - ${count} 次点击`;
}, [title]); // 缺少 count 依赖// ✅ 正确:使用函数式更新避免依赖
useEffect(() => {const timer = setInterval(() => {setCount(prevCount => prevCount + 1); // 不需要 count 依赖}, 1000);return () => clearInterval(timer);
}, []); // 空依赖数组

2、处理对象和函数依赖

function UserProfile({ user }) {// 使用 useMemo 记忆化对象const userStatus = useMemo(() => ({isActive: user.active,statusText: user.active ? '活跃' : '非活跃'}), [user.active]); // 只有当 user.active 变化时重新计算// 使用 useCallback 记忆化函数const updateUser = useCallback((updates) => {// 更新用户逻辑}, [user.id]); // 依赖 user.iduseEffect(() => {// 使用记忆化的值和函数console.log(userStatus);updateUser({ lastLogin: new Date() });}, [userStatus, updateUser]); // 依赖记忆化的值return <div>用户状态: {userStatus.statusText}</div>;
}

六、useEffect的进阶用法

1、多个useEffect的使用

function ComplexComponent({ userId, autoRefresh }) {const [user, setUser] = useState(null);const [notifications, setNotifications] = useState([]);// 获取用户数据useEffect(() => {fetchUser(userId).then(setUser);}, [userId]);// 获取通知(依赖用户数据)useEffect(() => {if (user) {fetchNotifications(user.id).then(setNotifications);}}, [user]); // 依赖 user// 自动刷新通知useEffect(() => {if (!autoRefresh || !user) return;const intervalId = setInterval(() => {fetchNotifications(user.id).then(setNotifications);}, 30000);return () => clearInterval(intervalId);}, [autoRefresh, user]); // 依赖 autoRefresh 和 userreturn (<div>{/* 渲染逻辑 */}</div>);
}

2、适用自定义Hook封装useEffect

// 自定义 Hook:使用防抖的搜索
function useDebounce(value, delay) {const [debouncedValue, setDebouncedValue] = useState(value);useEffect(() => {const handler = setTimeout(() => {setDebouncedValue(value);}, delay);return () => {clearTimeout(handler);};}, [value, delay]); // 依赖 value 和 delayreturn debouncedValue;
}// 在组件中使用
function SearchComponent() {const [query, setQuery] = useState('');const [results, setResults] = useState([]);const debouncedQuery = useDebounce(query, 500);useEffect(() => {if (debouncedQuery) {searchAPI(debouncedQuery).then(setResults);} else {setResults([]);}}, [debouncedQuery]); // 依赖防抖后的查询return (<div><inputvalue={query}onChange={(e) => setQuery(e.target.value)}placeholder="搜索..."/><ul>{results.map(result => (<li key={result.id}>{result.name}</li>))}</ul></div>);
}

七、常见问题与解决方案

1、无限循环问题

// ❌ 错误:导致无限循环
const [count, setCount] = useState(0);useEffect(() => {setCount(count + 1); // 每次渲染都会更新 count,触发重新渲染
}, [count]); // 依赖 count// ✅ 正确:使用函数式更新或无依赖
useEffect(() => {setCount(prevCount => prevCount + 1); // 不依赖外部 count 值
}, []); // 空依赖数组

2、异步操作处理

function AsyncComponent() {const [data, setData] = useState(null);useEffect(() => {let isMounted = true; // 跟踪组件是否挂载const fetchData = async () => {try {const result = await fetch('/api/data');const jsonData = await result.json();if (isMounted) {setData(jsonData); // 只在组件仍挂载时更新状态}} catch (error) {if (isMounted) {console.error('获取数据失败:', error);}}};fetchData();return () => {isMounted = false; // 组件卸载时设置为 false};}, []);return <div>{data ? data.message : '加载中...'}</div>;
}

3、依赖函数的问题

function ProblematicComponent() {const [count, setCount] = useState(0);const logCount = () => {console.log('当前计数:', count);};// ❌ 问题:logCount 在每次渲染都是新函数useEffect(() => {logCount();}, [logCount]); // 导致每次渲染都执行// ✅ 解决方案1:将函数移到 useEffect 内部useEffect(() => {const logCount = () => {console.log('当前计数:', count);};logCount();}, [count]); // 只依赖 count// ✅ 解决方案2:使用 useCallback 记忆化函数const logCountMemoized = useCallback(() => {console.log('当前计数:', count);}, [count]); // 依赖 countuseEffect(() => {logCountMemoized();}, [logCountMemoized]); // 依赖记忆化的函数return <button onClick={() => setCount(c => c + 1)}>增加</button>;
}

八、性能优化技巧

1、条件执行Effect

function ExpensiveComponent({ data, shouldProcess }) {useEffect(() => {if (shouldProcess) {// 只有 shouldProcess 为 true 时才执行昂贵操作performExpensiveOperation(data);}}, [data, shouldProcess]); // 仍然声明所有依赖
});

2、使用useMemo优化依赖

function OptimizedComponent({ items, filter }) {// 使用 useMemo 避免不必要的重新计算const filteredItems = useMemo(() => {return items.filter(item => item.includes(filter));}, [items, filter]); // 只有当 items 或 filter 变化时重新计算// effect 只依赖记忆化的值useEffect(() => {console.log('过滤后的项目:', filteredItems);}, [filteredItems]); // 依赖记忆化的数组return (<ul>{filteredItems.map(item => (<li key={item}>{item}</li>))}</ul>);
}

3、避免不必要的Effect

// ❌ 不必要的 effect:可以在渲染期间直接计算
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');useEffect(() => {setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);// ✅ 更好的方案:在渲染期间直接计算
const fullName = `${firstName} ${lastName}`;

九、最佳实践总结

1、明确依赖: 始终声明所有effect中使用的依赖项
2、适当清理: 对于订阅、定时器等,一定要提供清理函数
3、分离关注点: 使用多个useEffect分离不同的逻辑
4、避免无限循环: 谨慎设置状态,避免创建渲染循环
5、性能优化: 使用useMemo和useCallback优化依赖项
6、条件执行: 在effect内部添加条件判断,避免不必要的执行
7、异步处理: 正确处理异步操作的清理和竞态条件

总结

useEffect 是React函数组件的核心Hook,它使得副作用管理变得更加声明式和可预测。通过理解其执行机制、正确使用依赖数组、实现适当的清理逻辑,你可以编写出高效、可靠的React组件。

记住,useEffect 的核心思想是将副作用与渲染逻辑分离,让组件更专注于渲染UI,而将副作用操作放在统一的地方管理。这种分离使得代码更容易理解、测试和维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/96692.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96692.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/96692.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Yapi中通过MongoDB修改管理员密码与新增管理员

如何在Docker部署的Yapi中通过MongoDB修改管理员密码与新增管理员便捷管理Yapi权限&#xff1a;无需前端重置&#xff0c;直接通过数据库操作修改密码及添加管理员一、进入MongoDB容器并连接数据库 首先&#xff0c;通过以下命令进入运行中的MongoDB容器&#xff1a; docker ex…

【EasyR1】GRPO训练

官方github&#xff1a;https://github.com/hiyouga/EasyR1 参考&#xff1a;https://opendeep.wiki/hiyouga/EasyR1/quickstart 代码和环境配置 github&#xff1a;https://github.com/hiyouga/EasyR1 新建一个虚拟环境&#xff1a; python -m venv easyr1 source easyr1/b…

2025年KBS SCI1区TOP,新颖奖励与ε-贪婪衰减Q-learning算法+局部移动机器人路径规划,深度解析+性能实测

目录1.摘要2.新颖奖励与ε-贪婪衰减Q-learning算法3.结果展示4.参考文献5.代码获取6.算法辅导应用定制读者交流1.摘要 路径规划是移动机器人的核心任务&#xff0c;需要在高效导航的同时规避障碍。本文提出了一种改进Q-learning算法——定制化奖励与ε-贪婪衰减Q-learning&…

运行npm run命令报错“error:0308010C:digital envelope routines::unsupported”

下载的前后端分离架构的开源项目&#xff0c;运行“npm run serve”命令启动前端服务时报错“error:0308010C:digital envelope routines::unsupported”&#xff0c;这个错误通常是由于Node.js版本与项目依赖不兼容导致的&#xff0c;特别是在Node.js v17版本中&#xff0c;百…

AI计算提效关键。自适应弹性加速,基于存算架构做浮点运算

一、自适应弹性加速是提升芯片能效比的有力手段自适应弹性加速技术是现代芯片设计中提升能效比的关键路径之一。它摒弃了传统芯片在设计时采用的静态、固化的资源分配与功能设定模式&#xff0c;通过引入动态调整机制&#xff0c;使得芯片能够根据实时的应用需求和负载变化&…

Spring Boot测试陷阱:失败测试为何“传染”其他用例?

一个测试失败&#xff0c;为何“传染”其他测试&#xff1f;——Spring Boot 单元测试独立性与泛型陷阱实战解析 &#x1f6a9; 问题背景 在日常开发中&#xff0c;我们常会遇到这样的场景&#xff1a; 正在开发新功能 A&#xff0c;写了一个 testFeatureA() 测试方法&#xff…

Web开发中的CGI:通用网关接口详解

一、CGI的设计意图&#xff1a;解决Web的"静态"困境 在CGI出现之前&#xff0c;Web服务器只能做一件事&#xff1a;返回预先写好的静态文件&#xff08;HTML、图片等&#xff09;。每个用户看到的内容都是一模一样的。 设计意图很简单但却革命性&#xff1a; 让Web服…

在 SSMS 中查找和打开已保存的查询文件

在 SSMS 中查找和打开已保存的查询文件 在 SQL Server Management Studio (SSMS) 中&#xff0c;您可以轻松地查找并打开已保存的查询文件&#xff08;通常以 .sql 扩展名保存&#xff09;。SSMS 提供了直观的界面支持直接打开这些文件&#xff0c;无需额外工具。以下是详细步骤…

Protues使用说明及Protues与Keil联合仿真实现点亮小灯和流水灯

目录 1Protues介绍及新建工程 1.1进入软件 1.2文件创建 1.3默认选项 1.5设计面板 1.6添加元器件 1.7终端模式 1.8激励源模式 1.9探针模式 1.10仪表 1.11二维直线 1.12字符 2 Protues电路原理图仿真 2.1 220V交流电转5V直流电稳压电路仿真原理图 2.1.1 仿真原理图…

Linux PCI 子系统:工作原理与实现机制深度分析

Linux PCI 子系统&#xff1a;工作原理与实现机制深度分析 1. Linux PCI 子系统基础概念 1.1 PCI/PCIe 基础概念回顾 总线拓扑&#xff1a; PCI/PCIe 系统是一个树形结构。CPU 连接到 Root Complex (RC)&#xff0c;RC 连接至 PCIe 交换机 (Switch) 和 PCIe 端点设备 (Endpoint…

RabbitMQ 全面指南:架构解析与案例实战

目录一、RabbitMQ 简介1.1 什么是 RabbitMQ1.2 RabbitMQ 的核心组件1.3 RabbitMQ 的应用场景二、环境搭建2.1 安装 RabbitMQ2.2 安装 Erlang2.3 配置 RabbitMQ三、RabbitMQ 核心概念与工作原理3.1 消息模型3.2 交换机类型3.3 队列特性3.4 消息确认机制四、Spring Boot 集成 Rab…

6.2 el-menu

一、 <el-menu>: 菜单组件&#xff0c;定义了侧边栏内部的具体导航项、层级结构和交互行为。<el-container><!-- 侧边栏容器 --><el-aside width"200px"><!-- 菜单内容 --><el-menu default-active"1" class"el-men…

Windows 笔记本实现仅关屏仍工作:一种更便捷的 “伪熄屏” 方案

在使用 Windows 笔记本作为临时服务器或需要后台持续运行程序时&#xff0c;我们常面临一个需求&#xff1a;关闭屏幕以节省电量或减少光污染&#xff0c;同时保持系统正常工作。然而&#xff0c;网络上流传的诸多方法往往存在局限&#xff0c;要么无法兼顾 “熄屏” 与 “工作…

Linux应急响应一般思路(二)

进程排查进程(Process)是计算机中的程序关于某数据集合上的一次运行活动&#xff0c;是系统进行资源分配和调度的基本单位&#xff0c;是操作系统结构的基础无论是在Windows系统还是Linux系统中&#xff0c;主机在感染恶意程序后&#xff0c;恶意程序都会启动相应的进程&#x…

基于 SkyWalking + Elasticsearch + Grafana 的可落地调用链监控方案

这个方案成熟稳定、社区活跃、部署相对简单,非常适合中小型团队作为第一代调用链系统落地。 一、核心组件选型与角色 组件 版本建议 角色 优点 Apache SkyWalking v9.x+ 核心平台 (采集、分析、存储、UI) 国产优秀,Java Agent无侵入接入,功能全面,性能损耗低 Elasticsearc…

APP逆向——某站device-id参数

免责声明本博客所涉及的 爬虫技术、逆向分析方法 仅用于 学习、研究和技术交流。文中所有示例代码、工具和方法&#xff0c;均不得用于以下行为&#xff1a;未经授权的数据采集侵犯他人知识产权干扰或破坏正常业务系统任何违反国家法律法规的行为因读者将本教程内容用于 非法用…

C/C++数据结构之循环链表

概述循环链表本质上也是一个单向或双向链表&#xff0c;但其最后一个节点的指针并不指向NULL&#xff0c;而是指向链表的第一个节点&#xff0c;从而形成一个闭合的环。这种结构使得在遍历链表时&#xff0c;可以从任意一个节点开始&#xff0c;并最终回到起始点。音乐播放软件…

Mongodb的教程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、mongodb是什么&#xff1f; 二、mongodb的下载与安装教程 三、mongodb的常见操作 总结 前言 在当今数据驱动的世界中&#xff0c;数据库技术是构建高效…

MySQL视图有什么用?一文读懂虚拟表的六大核心价值

引言 在数据库开发中&#xff0c;你是否遇到过这样的困境&#xff1a;业务人员需要查看复杂关联数据却难以理解多表JOIN&#xff0c;或需要限制某些用户只能访问特定字段&#xff1f;MySQL视图正是为此设计的"数据透视镜"。本文将通过官方定义、典型场景和最佳实践&a…

ubuntu24.04 frps服务器端自动启动设置【2025-08-20】

Ubuntu 24.04采用systemd作为默认的init系统&#xff0c;我们可以通过创建systemd服务单元文件来实现开机自启动。以下是具体实施步骤&#xff1a;创建服务文件使用文本编辑器创建服务配置文件&#xff1a;sudo nano /etc/systemd/system/frps.service编写服务配置内容在文件中…