【React】useMemo 和 useEffect 的用法 - 实践

news/2025/11/8 19:53:47/文章来源:https://www.cnblogs.com/yangykaifa/p/19202955

两者的核心区别在于“何时执行、做什么、返回什么

作用

useMemo:在渲染期间计算并缓存一个“值”。目的是避免重复的昂贵计算或保持引用稳定(对象/数组/派生结果)。
useEffect:在渲染提交到 DOM 之后执行“副作用”。目的是与外部世界交互或处理需要在渲染后进行的事情(订阅、请求、操作 DOM、日志等)。

执行时机

useMemo:在组件渲染过程中同步运行计算(不触发浏览器绘制前就算完),返回值直接用于本次渲染。
useEffect:在浏览器完成绘制后异步运行(非阻塞渲染)。不会影响本次渲染的输出,只在提交后执行。

返回值

useMemo:返回计算结果(任何值:对象、数组、数字、函数等)。
useEffect:不返回值给渲染;可以返回一个清理函数用于卸载或依赖变化时清理副作用。

依赖变化时的行为

useMemo:依赖不变则复用上次结果;依赖变则在下一次渲染时重新计算值。
useEffect:依赖不变则不重新执行;依赖变则在提交后执行清理函数(若有)然后再运行副作用。

适用场景

useMemo:
重计算优化(过滤、排序、派生数据)。
保持引用稳定,避免把“每次都新建的对象/数组”传给子组件导致不必要渲染。
useEffect:
发起网络请求、订阅/取消订阅、事件监听、操作 DOM、计时器、日志、与外部存储交互等“副作用”。

常见误用对比

不要用 useEffect 去“计算一个值再 setState”来参与同一轮渲染,这会导致额外渲染;应使用 useMemo 在渲染期间直接算出值。
不要用 useMemo 做副作用(如请求、打印日志、修改外部变量);useMemo 仅用于纯计算,不能产生副作用。

与性能相关

useMemo 自身有开销,只有当计算成本高或引用稳定性能明显减少渲染时再用。
useEffect 异步执行,不阻塞渲染,但如果依赖频繁变化且在 Effect 中做重活,也会造成抖动或资源浪费。

简易判断

我只是需要一个由 props/state 派生出来的值,并且它应该在渲染时就可用且无副作用 → useMemo
我需要在组件渲染提交后与外部系统交互或安排清理 → useEffect

小例子:

useMemo:const sorted = useMemo(() => sort(items), [items])
useEffect: useEffect(() => { const id = setInterval(tick, 1000) return () => clearInterval(id) }, [])

实例直观理解

下面用两个并排对比的小例子,直观理解什么时候用 useMemo,什么时候用 useEffect + setState,以及为什么前者更合适做“纯派生计算”。

例子背景

有一份列表 allUsers,你可以通过 selectedRoles 来筛选用户。
我们在界面上展示筛选后的用户。

一、用 useEffect + setState(不太合适的做法)
说明:用 Effect 做纯计算,会导致多一次渲染;同时需要维护额外状态,容易产生同步问题。

代码片段

function Users({ allUsers, selectedRoles }) {
const [filteredUsers, setFilteredUsers] = useState(allUsers);
useEffect(() => {
// 纯计算:根据依赖派生值
const next = selectedRoles.length
? allUsers.filter(u => selectedRoles.includes(u.role))
: allUsers;
setFilteredUsers(next);
}, [allUsers, selectedRoles]);
return (
{filteredUsers.map(u =>
{u.name} - {u.role}
)}
); }

渲染流程(关键差异)
第1步:组件渲染,filteredUsers 还是上一次的值或初始值。
第2步:提交到 DOM 后,useEffect 运行,计算 next,然后 setFilteredUsers。
第3步:因为 setState,又触发一次重新渲染,UI 才显示最新的筛选结果。
问题

多了一次渲染。
如果有多个类似派生状态,维护依赖、初始值和更新顺序更复杂。
这段逻辑并无副作用,纯粹是算值,不该放在 Effect。

二、用 useMemo(更合适的做法)
说明:在渲染期间直接计算派生值,避免额外渲染,也不需要维护额外状态。

代码片段:

function Users({ allUsers, selectedRoles }) {
const filteredUsers = useMemo(() => {
return selectedRoles.length
? allUsers.filter(u => selectedRoles.includes(u.role))
: allUsers;
}, [allUsers, selectedRoles]);
return (
{filteredUsers.map(u =>
{u.name} - {u.role}
)}
); }

渲染流程
第1步:组件渲染时,同步计算 filteredUsers(依赖不变则复用缓存)。
第2步:一次渲染就得到正确的 UI,不会再触发额外渲染。
结论

对于“由 props/state 派生出的值”且没有副作用,优先用 useMemo。
你的 filtered = selectedBranches… 这种就是典型的派生计算,应改为 useMemo。

三、什么时候必须用 useEffect(给你一个需要副作用的对比例子)

情景:筛选结果不仅要显示,还要同步到地址栏(URL 查询参数),或写入 localStorage。这就属于副作用,必须用 useEffect。

代码片段:

function Users({ allUsers, selectedRoles }) {
// 派生值还是用 useMemo
const filteredUsers = useMemo(() => {
return selectedRoles.length
? allUsers.filter(u => selectedRoles.includes(u.role))
: allUsers;
}, [allUsers, selectedRoles]);
// 用副作用同步到外部世界(URL)
useEffect(() => {
const params = new URLSearchParams(window.location.search);
params.set('roles', selectedRoles.join(','));
const nextUrl = ${window.location.pathname}?${params.toString()};
window.history.replaceState(null, '', nextUrl);
}, [selectedRoles]);
return (
{filteredUsers.map(u =>
{u.name} - {u.role}
)}
); }

要点

派生值: 用 useMemo(不产生副作用)。
与外部交互: 用 useEffect(副作用,如 URL、订阅、计时器、请求、DOM 操作)。
如果你用 useEffect 去算 filtered 再 setState,只是把纯计算绕了一圈,增加一次渲染;除非你确实需要把这个值作为“可独立修改的状态”暴露出去或做副作用,否则没必要。

四、再给一个误用示例,对比正确写法
误用:在 useEffect 里计算结果后 setState,然后这个状态只用于当前渲染。
正确:直接 useMemo 返回值,或直接在渲染里算(如果不需要缓存)。

误用:

useEffect(() => {
setFullName(${firstName} ${lastName});
}, [firstName, lastName]);

正确:

const fullName = useMemo(() => ${firstName} ${lastName}, [firstName, lastName]);
// 或者计算很轻:const fullName = ${firstName} ${lastName};

总结规则

我只是需要一个从现有数据派生出来的值、用于当前渲染 → useMemo(或直接算)。
我需要在渲染后做事(与外部交互、事件、计时、请求),或管理清理 → useEffect。
用 useEffect 做纯计算通常是不必要的,会多一次渲染。

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

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

相关文章

[LangChain] 15. 内存型向量库

回忆 RAG 关键步骤:文本切割 嵌入处理 存储向量数据库向量数据库可以分为这几种类型:内存型 本地自托管 云托管LangChain 内置了 MemoryVectorStore,这就是一个内存型向量库,用于将文档向量存储到内存中,适合本地…

完整教程:从架构师视角看 RPC:分布式系统的灵魂纽带

完整教程:从架构师视角看 RPC:分布式系统的灵魂纽带pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&qu…

题解:qoj8047 DFS Order 4

题意:给出 \(n,P\),现在问对于所有 \(n\) 个点且父亲编号小于儿子的树,对其进行 dfs 并且优先遍历编号小的子节点,问有多少种 dfs 序。对 \(P\) 取模。\(n\le 800\)。 做法: 首先考虑如何判定一种 dfs 序是否合法…

题解:qoj8047 DFS Order 4

题意:给出 \(n,P\),现在问对于所有 \(n\) 个点且父亲编号小于儿子的树,对其进行 dfs 并且优先遍历编号小的子节点,问有多少种 dfs 序。对 \(P\) 取模。\(n\le 800\)。 做法: 首先考虑如何判定一种 dfs 序是否合法…

Oracle数据库恢复检查脚本

Oracle数据库恢复检查脚本prompt +----------------------------------------------------------------------------+ prompt | Oracle Database Recovery Check Result | promp…

视野修炼-技术周刊第126期 | TypeScript #1

① 🤫spoilerjs ② 🏆 TypeScript 跃升至 \#1 - GitHub 上最常用的语言 ③ 中国法定假日查询库 ④ type-flag - 类型化命令行参数解析 ⑤ Node v24 已经是最新的LTS 版本 ⑥ TypingSVG ⑦ 腾讯 TDesign 组件库的 u…

详细介绍:FPGA 中的 AXI 总线介绍

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

深入解析:眼控交互:ErgoLAB新一代人机交互方式

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

大模型、智能体和MCP服务间的交互

大模型、智能体和MCP服务间的交互本文以从图书馆借书为例大模型LLM提供智能决策,基于权限结果智能体Agent协调流程,不包含业务逻辑图书馆MCP服务处理业务权限验证(能做什么)认证服务处理基础身份验证(谁),非MCP…

2025年国内成人自考机构口碑推荐排行榜单:权威解析与选择指南

摘要 2025年国内成人自考教育行业迎来新一轮发展机遇,随着终身学习理念的普及和职业晋升需求的增长,成人自考市场规模持续扩大。本文基于权威数据分析和用户口碑评价,为您呈现最新成人自考机构排行榜单,并提供详细…

大信息领域列式存储与云存储的融合发展

大信息领域列式存储与云存储的融合发展pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Mon…

2025年六安市成人自考机构口碑推荐排行榜

摘要 2025年,成人自考教育行业迎来快速发展,在职人士对学历提升需求激增。本文基于权威数据和用户口碑,综合评估国内成人自考机构,推出前十排名榜单,并提供详细比较表单,助您高效选择可靠机构。榜单重点突出服务…

分享一个Oracle 数据库信息收集脚本

分享一个Oracle 数据库信息收集脚本LINUX: #!/bin/sh ############################################################################## # 脚本名称:oracle_db_info_collector.sh # 脚本版本:V3.0 # 功能描述:Or…

2025年11月杭州集训记

前面的区域,以后再来探索吧。

Bash 入门指南-简介和常见命令

Bash 入门指南-简介和常见命令Bash 入门指南(第一部分):Bash 简介 概述 Bash(Bourne Again Shell)是目前 Unix 和 Linux 系统中最广泛使用的命令行解释器,也是绝大多数 Linux 发行版的默认 Shell。作为用户与操作…

最小多项式与线性递推

对角化 在众多 dp 问题中,我们经常可以用矩阵快速幂进行优化。更进一步地,如果这个递推矩阵是一个形如 \(A = \begin{pmatrix} 3 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 9 \end{pmatrix}\),矩阵…

Zabbix服务告警:More than 75% used in the configuration cache

Zabbix Server 在运行时,会将主机、监控项、触发器、模板等配置信息从数据库加载到内存中,以加快访问速度。这块内存区域就叫 配置缓(Configuration Cache)。该值可通过配置文件CacheSize进行调整。问题现象 随着监…

to kill a mocking bird

this book describe a English hero again but actually power is shared by people with violence not the mercy from the conscience. History tells us that humans live in Stockholm.

mounriver studio WINDOWS启动报错解决

mounriver studio WINDOWS启动报错解决解决方法卸载软件, 然后重新安装,安装完成之后不要选择启动软件。安装界面关闭之后,使用管理员权限打开软件,便不会报错

Linux 内核启动日志输出阶段分析

问题描述 在对比原理图后,发现打印日志的串口是UART2(GPIO3_A2 & GPIO3_A3),但设备树中只开启了UART0(GPIO1_C2&GPIO1_C3)/* 以下设备树内容来自多个设备树描述文件*/ &uart0 {pinctrl-names = "de…