852 字
4 分钟
useMemo

它缓存一次计算的结果#

const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab])

首次渲染执行一次计算;之后每次渲染,React 用 Object.is 逐个比较依赖,全没变就返回上次的结果。

先量,再决定要不要优化#

判断「昂贵」靠量,不靠感觉:

console.time('filter')
const visibleTodos = filterTodos(todos, tab)
console.timeEnd('filter')

官方给的参考是:除非你在创建或遍历上千个对象,否则通常不算昂贵;量出来累加到 1ms 以上才值得考虑。另外开发环境的数字不准(严格模式下组件渲染两次),要看生产构建的结果,最好再开一下 CPU 降速。

只有三种情况值得手写#

  • 计算确实慢,而且依赖很少变。
  • 结果作为 prop 传给被 memo 包起来的组件,需要引用稳定。
  • 结果要作为别的 Hook 的依赖。

其余情况加它没有收益,只是多一层要读的代码。反过来,一个「每次都新」的值就能击穿整个组件的记忆化,这才是更常见的性能问题来源。

配合 memo 才有意义#

const List = memo(function List({ items }) {
// ...
})
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab])
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
)
}

不加 useMemo,filterTodos 每次返回新数组,memo 就永远拦不住,List 每次重渲染。

React Compiler 已经能做这件事#

官方在 useMemo 参考页开头就放了注记:React Compiler 会自动记忆化值与函数,减少手写 useMemo 的需要。Compiler 1.0 于 2025-10 稳定(发布说明)。

官方对现有代码的建议是:已有的记忆化先别急着删,删之前要仔细测,因为删掉可能改变编译结果;新代码可以靠编译器,需要精确控制的地方再手写 Hook。

所以现在的判断顺序是:先看项目有没有开编译器;开了就先写干净的代码,遇到瓶颈再量、再加。

先试这几件更有效的事#

官方的建议是先改结构,再谈记忆化:

  • 组件只是包一层别的组件时,让它接收 JSX 作为 children。这样它自己的状态变化不会带着子组件一起重渲染。
  • 状态就近放,别动不动提到顶层或全局。
  • 渲染逻辑保持纯。重渲染会出问题,那是 bug,修 bug 而不是加缓存。
  • 去掉那些「用 Effect 更新 state」的写法。React 应用里的性能问题,大多来自这类链式更新。

四条边界#

只能在组件或自定义 Hook 的顶层调用,不能在循环或条件里。要在循环里缓存每一项,把每一项抽成组件。

计算函数必须是纯的:不改 props、不改 state。改自己新建的对象没问题。

别把它当语义保证。官方明确说它只是性能优化:开发环境改文件、或组件初次挂载被挂起时,React 会丢掉缓存。代码不能依赖「一定被缓存」,需要真值的地方用 state 或 ref。

别在计算里做副作用。请求、改 DOM、改 state 都属于 Effect 或事件处理函数。

最后是一个常见手误:useMemo(() => { ... }) 少了第二个参数时,每次渲染都会重算;想在箭头函数里直接返回对象,要写成 () => ({ ... }),或者老实写 return。

第 26 篇,共 39 篇本文属于系列

useMemo
https://www.choria.top/posts/hook-use-memo/
作者
乔瑞雅·克林威尔
发布于
2026-05-02
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 190 天

部分内容可能已过时