如何读懂why-did-you-render日志?props/state/hooks差异报告逐行详解
2026/9/19 23:03:37 网站建设 项目流程

如何读懂why-did-you-render日志?props/state/hooks差异报告逐行详解

【免费下载链接】why-did-you-renderwhy-did-you-render by Welldone Software monkey patches React to notify you about potentially avoidable re-renders. (Works with React Native as well.)项目地址: https://gitcode.com/gh_mirrors/wh/why-did-you-render

why-did-you-render是一款用于排查 React 组件「多余重渲染」的开发工具。它通过 monkey patch 的方式监听组件更新,在控制台输出一条条结构化的why-did-you-render 日志,告诉你组件为什么会重新渲染——是 props 变了、state 变了,还是某个 hooks 的结果变了。很多新手第一次看到这些日志会一头雾水:props.styledifferent objects that are equal by valueprev !== next……这些到底在说什么?

这篇文章就用一条真实的props/state/hooks 差异报告为例,逐行拆解每个字段的含义,并附上一份7 种差异类型速查表,帮你在 10 分钟内看懂为什么组件会重渲染,以及该往哪个方向去优化。


🖥️ 先认识一条完整的 why-did-you-render 日志

下图是官方 Demo 中BigListPureComponent触发重渲染时的控制台输出,也是我们本次逐行拆解的主角:

一条日志由三个层级组成,从外到内分别是:

  1. 组件名分组:蓝色加粗的BigListPureComponent,点开(或展开)后才是它的报告内容;
  2. 原因说明Re-rendered because of props changes:,即"因为 props 变化而重渲染";
  3. 差异明细分组props.style这一行,标明具体是哪个属性引发了差异。

💡 日志之所以用console.group折叠,就是为了让多个组件的报告互不干扰。想看某一条时,点击左侧的 ▼ 箭头展开即可。日志分组逻辑写在 src/defaultNotifier.js,而具体某一层 props/state/hooks 的 diff 文本则由 src/printDiff.js 负责渲染。


🔍 逐行拆解一条 props 差异报告

对照上图,我们把props.style分组里的一行行文字拆开看:

第 1 行:原因前缀Re-rendered because of props changes

这句话告诉你重渲染的"大类"来源,只有三种可能:

  • Re-rendered because of props changes:props触发的
  • And because of state changes:state触发的("And" 表示这是在同一次渲染里的第二个原因)
  • And because of [hook useMemo result] changes:hooks触发的,方括号里就是具体的 hook 名

如果你同时改了 props 和 state,日志会先打印 props 段落,再打印 state 段落。

第 2 行:差异路径props.style

这一行是"坐标",精确到引发差异的字段路径。它由 diff 算法递归计算得出,源码在 src/calculateDeepEqualDiffs.js 中的accumulateDeepEqualDiffs函数:

  • props.style—— props 下有个叫style的字段变了;
  • props.list[2].name—— 列表第 3 个元素的name变了(数组用[索引]);
  • [hook useMemo result]—— 某个 hook 的返回结果整体变了。

路径越具体,你改起来越省力:它直接指向了"该在哪个 prop 上做 memo 或稳定化"。

第 3 行:差异类型描述

这一行是整份报告的"诊断结论",共有7 种措辞,对应源码里 src/consts.js 的diffTypesDescriptions常量。先记住最常见的三种:

日志里的英文含义该怎么想
different objects两个值真的不一样(如1vs2属于"合法重渲染",通常不用优化
different objects that are equal by value引用不同,但内容一样(如{width:'100%'}每次新建)⚠️ 这是可以优化的信号,重点排查
different functions with the same name两个同名函数引用不同(如每次 render 都新建的onClickuseCallback/React.memo稳定引用

🎯 经验法则:看到equal by valuedifferent functions,基本就是"可以避免的重渲染";看到different objects,多半是 props 真的变了,属于正常渲染。

第 4 行:prev 'style' !== next 'style'

这一行把前一次下一次的值并排打印出来,格式固定为:

prev 'style': {width: "100%"} !== {width: "100%"} :next 'style'

左右两边看起来一模一样,正是equal by value的典型现场——每次 render 都新建了一个 style 对象。此时如果该组件是React.memo包裹的,这次重渲染其实是"白干"的。

附赠一行:For detailed diff...

当两个对象深度相等时,日志还会多打一行:

For detailed diff, right click the following fn, save as global, and run:

意思是:控制台里那个函数你右键 → Save as global,再执行它,就能看到对象内部逐字段的对比。这是给"对象很深、肉眼看不出差异"的场景准备的进阶工具,实现见 src/printDiff.js 第 56 行附近。


📋 7 种差异类型(diffType)速查表

这份表建议收藏,覆盖 why-did-you-render 日志可能出现的所有差异措辞。数据来源:src/consts.js 第 11–19 行。

日志措辞场景举例优先级
different objectsa={1}a={2}低(合理渲染)
different objects that are equal by valuestyle={{width:'100%'}}每次新建⭐ 高
different date objects with the same value两个new Date()时间相同
different regular expressions with the same value每次 render 都new RegExp(...)
different React elements (...)直接把<jsx/>当 props 传,语法糖每次产生element⭐ 高
different functions with the same name内联箭头函数onClick={() => ...}⭐ 高
same objects by ref (===)引用完全相同(通常不会作为 diff 打印)

看到different React elements这条尤其要注意:只要你在 JSX 里直接写<Child/>作为某个 prop,父组件一渲染,子组件必然收到全新 element。解法通常是把这个 element 用useMemo稳定住,或让子组件自己从 context 取值。


🪝 state 与 hooks 差异报告怎么读

state 变化

结构和 props 完全一致,只是分组名从props.xxx变成state.xxx,前缀也会从 "Re-rendered because" 变成 "And because"。

一个反直觉的提示:如果你看到日志写state object itself changed but its values are all equal,通常意味着你调用了setState,但新 state 和旧 state 值完全相同——这是典型的"无意义的 setState",可以直接删掉或加判断。

hooks 结果变化

如果组件里的useState/useContext/useReducer/useMemo等 hook 结果变了,日志的分组名会变成[hook useMemo result]这种形式,方括号里的名字就是 hook 名。

useContext为例:如果<Provider value={{a:1}}>每次都新建 value 对象,即使值内容一样,所有useContext组件都会收到"深度相等但引用不同"的 diff。这个 hook 追踪逻辑在 src/patches/ 目录下的patchFunctionalOrStrComponent.jspatchClassComponent.js等文件中实现,默认开启(trackHooks: true)。


🧭 读懂 logOwnerReasons:Rendered by Owner区块

默认开启的logOwnerReasons: true会在报告末尾追加一个"Rendered by Owner"子分组,回答一个问题:"是哪个父组件把我逼着重渲染的?"

上图来自官方 Demo,信息非常直白:

  1. Child的 props 对象整体变了,但值都一样→ 本身可以优化;
  2. 展开Rendered by Owner后,看到真正的原因是Ownerprops.a1变成了2
  3. 顺着这条线索往上看,就能定位到"到底是谁在改 a"。

这段"向上溯源"的能力,来自 src/getUpdateInfo.js 里的getOwnerDifferences函数——它会把父组件(owner)的 props/state/hooks 也做一次 diff,一并打进日志。

🧠读日志的正确顺序:先看当前组件 diff → 再看Rendered by Owner里的父组件 diff → 决定是"在父组件里稳定化",还是"在当前组件里改用React.memo"。


🚀 常见日志场景 & 对应解法(速查)

日志关键句常见原因建议动作
the props object itself changed but its values are all equal父组件重渲染 → 传给子组件的 props 被重建React.memo+useCallback/useMemo稳定化
different React elements (...)直接把 JSX 作为 prop 传递useMemo包住那个 element,或改走 context
different functions with the same name内联函数、事件处理器每次新建useCallback或提取到组件外部
Re-rendered although props and state objects are the same组件内部调用了forceUpdate()或类似强制更新检查forceUpdate调用点,尽量替换成 state 变化
只报different objects且值确实不同数据源真的变了属于合理渲染,无需优化(除非频率过高)

forceUpdate那一行的文案,来自 src/defaultNotifier.js 第 168–175 行——当 props、state、hooks全都没有变化时,WDYR 会明确告诉你"多半是有人调了forceUpdate"。


🧯 高效阅读 WDYR 日志的 4 条建议

  1. 优先看equal by valuedifferent functions:这是"白渲染"的高发区,改起来性价比最高。
  2. 先展开Rendered by Owner:搞清楚源头在哪一层,再决定在哪一层做 memo,避免"哪里都包一遍"。
  3. 结合路径精准修改props.styleprops.list[0].name这种路径就是"手术刀",只对那一处做useMemo/useCallback,不要盲目整体套。
  4. 配合 React DevTools Profiler 验证:改完 memo 后,用官方 Profiler 测量实际收益,避免过度优化拖慢业务代码。相关讨论可参考 README.md 里的 "Integration" 与 "Options" 章节,特别是logOnDifferentValuescollapseGroupsonlyLogs这几个能显著影响日志可读性的开关。

结语

why-did-you-render 日志看似信息密集,其实结构非常规整:组件名分组 → 原因前缀 → 差异路径 → 差异类型描述 → prev/next 对比,再配上Rendered by Owner的溯源信息,就能把"为什么重渲染"这条链路完整还原。

把这篇文章的7 种 diffType 速查表日志场景速查表收藏起来,下次再看到控制台里蓝色的Re-rendered because...,你只需要 30 秒,就能判断出:

  • 这次渲染值不值得优化
  • 该在哪一层动刀;
  • useMemo/useCallback/React.memo中的哪一个去稳定化。

祝你的 React 应用少"oops",多"smooth" 🚀

【免费下载链接】why-did-you-renderwhy-did-you-render by Welldone Software monkey patches React to notify you about potentially avoidable re-renders. (Works with React Native as well.)项目地址: https://gitcode.com/gh_mirrors/wh/why-did-you-render

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询