
它缓存一次计算的结果
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。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 190 天
部分内容可能已过时