当前位置: 首页 > news >正文

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

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

作用

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.jsqmd.com/news/35284/

相关文章:

  • [LangChain] 15. 内存型向量库
  • 完整教程:从架构师视角看 RPC:分布式系统的灵魂纽带
  • 题解:qoj8047 DFS Order 4
  • Oracle数据库恢复检查脚本
  • 视野修炼-技术周刊第126期 | TypeScript #1
  • 详细介绍:FPGA 中的 AXI 总线介绍
  • 深入解析:眼控交互:ErgoLAB新一代人机交互方式
  • 大模型、智能体和MCP服务间的交互
  • 2025年国内成人自考机构口碑推荐排行榜单:权威解析与选择指南
  • 大信息领域列式存储与云存储的融合发展
  • 2025年六安市成人自考机构口碑推荐排行榜
  • 分享一个Oracle 数据库信息收集脚本
  • 2025年11月杭州集训记
  • Bash 入门指南-简介和常见命令
  • 最小多项式与线性递推
  • Zabbix服务告警:More than 75% used in the configuration cache
  • to kill a mocking bird
  • mounriver studio WINDOWS启动报错解决
  • Linux 内核启动日志输出阶段分析
  • Python 潮流周刊#126:新一代静态网站生成器
  • 第二章数据预处理:公式Python代码完成
  • 《代码大全 2》观后感(七):代码重构 —— 让代码 “永葆青春”
  • 吸哎四匹 2025 游击
  • 百度网盘把Windows下的习惯带进了Linux
  • 深入解析:MySQL 存储引擎深度解析:InnoDB 架构与配置优化指南
  • 做题记录(Nov.)
  • 2025年安徽猪肉批发厂家口碑排行TOP5
  • [国家集训队] 飞飞侠 题解
  • 插槽vue/react - 详解
  • AI元人文的思想锻造之路:从“空白承诺”到“动力之源”