R35第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

memo 与属性稳定性

memo 与属性稳定性 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

读完你能解释「父组件一渲染,列表所有子项跟着重渲染」的原因,会用 React.memo 把重渲染挡在组件边界之外,并用 useCallback / useMemo 稳定传下去的属性引用;同时掌握验证习惯:优化后渲染次数必须下降,而交互结果必须和优化前完全一致。

问题场景

课程收藏页:顶部一个搜索框,下面 60 张课程卡片。在搜索框里每敲一个字,React DevTools 的高亮闪成一片——60 张卡片全部重渲染,尽管它们的 props 一个都没变。原因在于 React 的默认策略:父组件渲染时,所有子组件默认跟着渲染,不管 props 变没变。列表越长,这个「陪着渲染」的浪费越明显。

核心解释

memo 是组件级的缓存:组件包上 memo 后,React 在父组件渲染时会先对 props 做一次浅比较,全部相同就跳过本次渲染,直接复用上一次的渲染结果:

const CourseCard = memo(function CourseCard({ course, onFav }) {
  console.log("render:", course.title); // 观察渲染次数
  return (
    <div>
      {course.title} <button onClick={() => onFav(course.id)}>收藏</button>
    </div>
  );
});

但浅比较救不了「每次渲染都新建」的值:内联箭头函数、内联对象字面量、filter 出来的新数组,每次渲染都是新引用,浅比较必然不相等,memo 等于白包——这是最常见的「假优化」。所以要配合引用稳定化:

const hot = useMemo(() => courses.filter((c) => c.hot), [courses]); // 稳定数据引用
const handleFav = useCallback((id) => toggleFav(id), [toggleFav]);  // 稳定函数引用

useMemo 稳定数据引用,useCallback 稳定函数引用,让到达 memo 组件的 props 真正「没变」。这正是上一课说的维护成本现场:依赖数组写漏一项,稳定性破功,memo 失效——所以每一步都要验证,不能包完就信。

验证手段有两个:组件里的渲染计数(console.log 或页面上的计数徽标),以及 React DevTools Profiler 的火焰图。优化后的回归检查只需两条:渲染次数降下来了;点收藏、输入关键词等交互的结果和优化前一致——memo 只改变「渲染不渲染」,不改变「结果对不对」。

示例拆解

实验区 60 张课程卡片 + 搜索框 + 三种模式:

  • 每张卡片上有小字显示自己累计渲染的次数(仅测量用,正式代码别这么写);
  • 「不加 memo」:输入一个字,60 张卡片计数全部 +1;
  • 「memo + 内联函数」:卡片包了 memo,但 onFav 还是每次渲染新建的箭头函数——照样全部 +1,这就是最容易踩的假优化;
  • 「memo + useCallback」:函数引用稳定了,输入时所有卡片计数都不动;
  • 验证:三种模式各输入几轮关键词,对比计数增长;再点几次收藏,确认收藏功能与优化前行为一致。

动手任务

打开本课实验区:

  1. 把 useCallback 的依赖数组从 [] 改成 [favs],再输入关键词:观察为什么所有卡片又重渲染了,说出依赖数组里「多余的项」如何破坏 memo。
  2. 给搜索框加一个「清空」按钮(只 setKeyword("")),确认 useCallback 模式下点击它所有卡片都不重渲染。
  3. 把 favs 传进卡片并显示收藏标记 ★,确认点击收藏时只有目标卡片重渲染,其余卡片计数不动。

常见错误

  • 包了 memo 却传内联函数/对象:浅比较每次不等,优化无效,还多了比较开销。
  • 为了 memo 把组件拆得过碎:边界太多,维护引用稳定性的代码比省下的渲染还贵;先测量(R34)再拆。
  • 优化后不做行为回归:依赖数组写错时可能出现「props 看起来没变而实际该变」,交互走一遍才能确认。
  • 以为 memo 能挡住 context 变化:memo 组件消费的 context 值变化时照样重渲染,memo 只比较 props。

检查与复盘

自查清单:

  • 能说清 memo 的浅比较语义和「内联函数/内联对象/新数组」三类假优化现场
  • 传给 memo 组件的函数和数据,知道分别用 useCallback / useMemo 稳定
  • 每次优化都验证过两件事:渲染次数下降,交互结果不变

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1读代码中等

阅读代码(React 18+)。App 里 count 每点一次加一。count 变化时,memo 包裹的 HeavyListMemo 会跳过重渲染吗?

function HeavyList({ config }) {
  // 渲染 1000 行列表
  return <ul>{/* ... */}</ul>;
}
const HeavyListMemo = memo(HeavyList);

function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <HeavyListMemo config={{ lang: 'zh' }} />
    </>
  );
}
Q2多选中等(多选,全部选对才算正确)

想让 memo 包裹的子组件真正跳过重渲染,下列哪些做法能保持 props 引用稳定?(多选)

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...