资讯详情

资讯详情

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

如何读懂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-renderwhy-did-you-render是一款用于排查 React 组件「多余重渲染」的开发工具。它通过 monkey patch 的方式监听组件更新在控制台输出一条条结构化的why-did-you-render 日志告诉你组件为什么会重新渲染——是 props 变了、state 变了还是某个 hooks 的结果变了。很多新手第一次看到这些日志会一头雾水props.style、different objects that are equal by value、prev ! next……这些到底在说什么这篇文章就用一条真实的props/state/hooks 差异报告为例逐行拆解每个字段的含义并附上一份7 种差异类型速查表帮你在 10 分钟内看懂为什么组件会重渲染以及该往哪个方向去优化。️ 先认识一条完整的 why-did-you-render 日志下图是官方 Demo 中BigListPureComponent触发重渲染时的控制台输出也是我们本次逐行拆解的主角一条日志由三个层级组成从外到内分别是组件名分组蓝色加粗的BigListPureComponent点开或展开后才是它的报告内容原因说明Re-rendered because of props changes:即因为 props 变化而重渲染差异明细分组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 都新建的onClick用useCallback/React.memo稳定引用 经验法则看到equal by value或different 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.js、patchClassComponent.js等文件中实现默认开启trackHooks: true。 读懂 logOwnerReasonsRendered by Owner区块默认开启的logOwnerReasons: true会在报告末尾追加一个Rendered by Owner子分组回答一个问题是哪个父组件把我逼着重渲染的上图来自官方 Demo信息非常直白Child的 props 对象整体变了但值都一样→ 本身可以优化展开Rendered by Owner后看到真正的原因是Owner的props.a从1变成了2顺着这条线索往上看就能定位到到底是谁在改 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.memouseCallback/useMemo稳定化different React elements (...)直接把 JSX 作为 prop 传递用useMemo包住那个 element或改走 contextdifferent 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 条建议优先看equal by value和different functions这是白渲染的高发区改起来性价比最高。先展开Rendered by Owner搞清楚源头在哪一层再决定在哪一层做 memo避免哪里都包一遍。结合路径精准修改props.style、props.list[0].name这种路径就是手术刀只对那一处做useMemo/useCallback不要盲目整体套。配合 React DevTools Profiler 验证改完 memo 后用官方 Profiler 测量实际收益避免过度优化拖慢业务代码。相关讨论可参考 README.md 里的 Integration 与 Options 章节特别是logOnDifferentValues、collapseGroups、onlyLogs这几个能显著影响日志可读性的开关。结语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),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →