why-did-you-render README

The project's README from github.com/welldone-software/why-did-you-render. As one markdown file: README.md.

Why Did You Render

npm version Build Status license @welldone-software/why-did-you-render Coverage Status

why-did-you-render by Welldone Software monkey patches React to notify you about potentially avoidable re-renders. (Works with React Native as well.)

For example, if you pass style={{width: '100%'}} to a big memo component it would always re-render on every element creation:

<MemoBigList style={{width: '100%'}}/>

It can also help you to simply track when and why a certain component re-renders.

[!CAUTION] The library was not tested with React Compiler at all. I believe it's completely incompatible with it.

[!CAUTION] Not all re-renders are "bad". Sometimes shenanigan to reduce re-renders can either hurt your App's performance or have a negligible effect, in which case it would be just a waste of your efforts, and complicate your code. Try to focus on heavier components when optimizing and use the React DevTools Profiler to measure the effects of any changes.

[!NOTE] I've joined the React team, specifically working on React tooling. This role has opened up exciting opportunities to enhance the developer experience for React users— and your input could offer valuable insights to help me with this effort. Please join the conversation in the discussion thread!

Setup

The latest version of the library was tested (unit tests and E2E) with React@19 only.

npm install @welldone-software/why-did-you-render --save-dev

or

yarn add @welldone-software/why-did-you-render -D

Set the library to be the React's importSource and make sure preset-react is in development mode.

This is because React 19 requires using the automatic JSX transformation.

['@babel/preset-react', {
  runtime: 'automatic',
  development: process.env.NODE_ENV === 'development',
  importSource: '@welldone-software/why-did-you-render',
}]

React Native

Bare workflow

Add the plugin as listed below and start react-native packager as usual. Default env for babel is "development". If you do not use expo when working with react-native, the following method will help you.

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],

  env: {
    development: {
      plugins: [['@babel/plugin-transform-react-jsx', {
        runtime: 'automatic',
        development: process.env.NODE_ENV === 'development',
        importSource: '@welldone-software/why-did-you-render',
      }]],
    },
  },
}

Expo managed

You can pass params to @babel/preset-react through babel-preset-expo

// babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: [
      [
        "babel-preset-expo",
        {
          jsxImportSource: "@welldone-software/why-did-you-render",
        },
      ],
    ],
  };
};

Notice: Create React App (CRA) ^4 uses the automatic JSX transformation. See the following comment on how to do this step with CRA

Create a wdyr.js file and import it as the very first import in your application.

wdyr.js:

import React from 'react';

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  });
}

[!CAUTION] The library should NEVER be used in production because:

  • It significantly slows down React
  • It monkey patches React and can result in unexpected behavior

In Typescript, call the file wdyr.ts and add the following line to the top of the file to import the package's types:

/// <reference types="@welldone-software/why-did-you-render" />

Import wdyr as the first import (even before react-hot-loader if you use it):

index.js:

import './wdyr'; // <--- first import

import 'react-hot-loader';

import React from 'react';
import ReactDOM from 'react-dom';
// ...
import {App} from './app';
// ...
ReactDOM.render(<App/>, document.getElementById('root'));

If you use trackAllPureComponents, all pure components (React.PureComponent or React.memo) will be tracked.

Otherwise, add whyDidYouRender = true to ad-hoc components to track them. (f.e Component.whyDidYouRender = true)

More information about what is tracked can be found in Tracking Components.

Can't see any WDYR logs? Check out the troubleshooting section or search in the issues.

Custom Hooks

Also, tracking custom hooks is possible by using trackExtraHooks. For example if you want to track useSelector from React Redux:

wdyr.js:

import React from 'react';

// For react-native you might want to use 
// the __DEV__ flag instead of process.env.NODE_ENV === 'development'
if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  const ReactRedux = require('react-redux');
  whyDidYouRender(React, {
    trackAllPureComponents: true,
    trackExtraHooks: [
      [ReactRedux, 'useSelector']
    ]
  });
}

Notice that there's currently a problem with rewriting exports of imported files in webpack. A quick workaround can help with it: #85 - trackExtraHooks cannot set property.

Read More

Integration With Other Libraries

Sandbox

You can test the library in the official sandbox.

And another official sandbox with hooks tracking

Tracking Components

You can track all pure components (React.PureComponent or React.memo) using the trackAllPureComponents: true option.

You can also manually track any component you want by setting whyDidYouRender on them like this:

class BigList extends React.Component {
  static whyDidYouRender = true
  render(){
    return (
      //some heavy render you want to ensure doesn't happen if its not necessary
    )
  }
}

Or for functional components:

const BigListPureComponent = props => (
  <div>
    //some heavy component you want to ensure doesn't happen if its not necessary
  </div>
)
BigListPureComponent.whyDidYouRender = true

You can also pass an object to specify more advanced tracking settings:

EnhancedMenu.whyDidYouRender = {
  logOnDifferentValues: true,
  customName: 'Menu'
}

Options

Optionally you can pass in options as the second parameter. The following options are available:

include / exclude

(default: null)

You can include or exclude tracking of components by their displayName using the include and exclude options.

For example, the following code is used to track all redundant re-renders that are caused by older React-Redux:

whyDidYouRender(React, { include: [/^ConnectFunction/] });

Notice: exclude takes priority over both include and manually set whyDidYouRender =

trackAllPureComponents

(default: false)

You can track all pure components (both React.memo and React.PureComponent components)

Notice: You can exclude the tracking of any specific component with whyDidYouRender = false

trackHooks

(default: true)

You can turn off tracking of hooks changes.

Understand and fix hook issues.

trackExtraHooks

(default: [])

Track custom hooks:

whyDidYouRender(React, {
  trackExtraHooks: [
    // notice that 'useSelector' is a named export
    [ReactRedux, 'useSelector'],
  ]
});

This feature is rewriting exports of imported files. There is currently a problem with that approach in webpack. A workaround is available here: #85 - trackExtraHooks cannot set property

logOwnerReasons

(default: true)

One way of fixing re-render issues is preventing the component's owner from re-rendering.

This option is true by default and it lets you view the reasons why an owner component re-renders.

demo

logOnDifferentValues

(default: false)

Normally, you only want logs about component re-renders when they could have been avoided.

With this option, it is possible to track all re-renders.

For example:

render(<BigListPureComponent a={1}/>)
render(<BigListPureComponent a={2}/>)
// will only log if you use {logOnDifferentValues: true}

hotReloadBufferMs

(default: 500)

Time in milliseconds to ignore updates after a hot reload is detected.

When a hot reload is detected, we ignore all updates for hotReloadBufferMs to not spam the console.

onlyLogs

(default: false)

If you don't want to use console.group to group logs you can print them as simple logs.

collapseGroups

(default: false)

Grouped logs can be collapsed.

titleColor / diffNameColor / diffPathColor / textBackgroundColor

(default titleColor: '#058')
(default diffNameColor: 'blue')
(default diffPathColor: 'red')
(default textBackgroundColor: 'white)

Controls the colors used in the console notifications

notifier

(default: defaultNotifier that is exposed from the library)

You can create a custom notifier if the default one does not suite your needs.

getAdditionalOwnerData

(default: undefined)

You can provide a function that harvests additional data from the original react element. The object returned from this function will be added to the ownerDataMap which can be accessed later within your notifier function override.

Troubleshooting

No tracking

Custom Hooks tracking (like useSelector)

There's currently a problem with rewriting exports of imported files in webpack. A quick workaround can help with it: #85 - trackExtraHooks cannot set property.

React-Redux connect HOC is spamming the console

Since connect hoists statics, if you add WDYR to the inner component, it is also added to the HOC component where complex hooks are running.

To fix this, add the whyDidYouRender = true static to a component after the connect:

  const SimpleComponent = ({a}) => <div data-testid="foo">{a.b}</div>)
  // not before the connect:
  // SimpleComponent.whyDidYouRender = true
  const ConnectedSimpleComponent = connect(
    state => ({a: state.a})
  )(SimpleComponent)
  // after the connect:
  SimpleComponent.whyDidYouRender = true

Sourcemaps

To see the library's sourcemaps use the source-map-loader.

Credit

Inspired by the following previous work:

License

This library is MIT licensed.

🔼Back to top!

why-did-you-render (welldone-software/why-did-you-render)

Searching Database…

这是什么?Why Did You Render(WDYR)会对 React 进行猴子补丁(monkey-patch),在控制台中记录某个组件何时以及为何重新渲染,即使它新的 props/state 与旧的相等(例如传入一个新创建的内联对象 style={{width: '100%'}})。
这是代码检查工具、构建插件,还是别的什么?它是一个小型 JavaScript 库,只需在应用入口处导入一次。它在运行时修补 React,并把重新渲染的诊断信息输出到浏览器控制台,而不是静态分析你的代码。
我该如何把它接入我的应用?创建一个 wdyr.js 文件,并把它作为应用中的第一条 import 语句引入;然后给需要追踪的组件设置 whyDidYouRender = true,或者启用 trackAllPureComponents,自动追踪所有 React.PureComponent 和 React.memo。
它需要特殊的 Babel 或 React 配置吗?需要。它目前适配 React 19,并且需要自动 JSX 转换(以 React 作为导入源,并在开发模式下使用 preset-react);如果用于 React Native,还需要一个 Babel 插件;lodash 是运行时依赖。
把它发布到生产环境安全吗?不安全。绝对不要在生产环境中使用此库,因为它会显著拖慢 React,并对 React 进行猴子补丁,可能导致意外行为;此外,它未经过 React Compiler 的测试,据信与其不兼容。
它只能用于网页,还是也能用于移动应用?它既可用于普通的 React 网页应用,也可用于 React Native,无论是裸工作流(bare workflow)还是 Expo 托管的项目。
这个工具能和新的 React Compiler 一起使用吗?不能。维护者明确警告,它从未经过 React Compiler 的测试,并认为二者完全不兼容。
它能追踪 Redux 的 useSelector 这类自定义 Hook 吗?可以。通过 trackExtraHooks 选项,你可以列出 useSelector 等自定义 Hook,以便在它们返回不同结果时记录日志;不过,目前存在一个已知的 webpack 问题,涉及重写导出(已追踪为 issue #85)。
它报告的每一次重新渲染都是真正的问题吗?不是。作者强调,并非所有被标记的重新渲染都有问题;如果每一次都去排查,可能会影响性能或徒增复杂度,收益却很小。因此应重点关注开销大的组件,并用 React DevTools Profiler 验证修复效果。
这个工具的灵感来自哪里?它的灵感来自更早的 why-did-you-update 库,以及一篇题为《React 性能调试深度解析》的博文。
Size
9.2 MB
Stars
12.5K
Age
7y
Commits · 90 Days