はじめに
「React.memoを付けたのに、なぜかコンポーネントが再レンダリングされ続けている」——Reactでパフォーマンスチューニングをしていると、こうした場面に必ず一度は出くわします。React DevToolsのProfilerで再レンダリングが起きていることは分かっても、「なぜ起きたのか」「本当に避けられたのか」までは教えてくれません。
そこで役立つのがWhy Did You Renderです。Reactを監視対象に組み込むだけで、「このコンポーネントは本来再レンダリング不要だったのに、propsの参照が変わったせいで再レンダリングされました」といった具体的な原因をコンソールに出力してくれます。今回はこのライブラリの使い方を、実際に動くサンプルとともに紹介します。
Why Did You Renderとは
Why Did You Render(WDYR)は、Reactを「モンキーパッチ」することで再レンダリングを監視し、避けられたはずの再レンダリングが発生したときにその理由をコンソールへ出力してくれる開発support用ライブラリです。welldone-software社が公開しており、React本体だけでなくReact Nativeにも対応しています。
主な特徴
- 原因の可視化 - propsやstateが「値としては同じなのに参照が変わった」ケースを検出し、変更前後の差分をコンソールに表示します
- 細かい追跡設定 - アプリ全体を一括で監視することも、特定のコンポーネントだけをピンポイントで監視することもできます
- フックの追跡にも対応 -
useStateやuseSelectorのようなカスタムフックが再レンダリングの原因になっていないかも追跡できます - 開発時のみ有効化できる -
NODE_ENVで分岐させることで、本番ビルドには含めずに済みます
インストール
npm、yarnどちらでも導入できます。開発時にのみ使うツールなので--save-dev(-D)を付けるのが基本です。
npm install @welldone-software/why-did-you-render --save-dev
yarn add @welldone-software/why-did-you-render --dev
導入後は、エントリーポイントより前に読み込まれるwdyr.ts(または.js)のようなファイルを用意し、アプリの一番最初でインポートします。
// wdyr.ts
import React from 'react';
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}
// index.tsx
import './wdyr'; // 必ず一番最初にインポートする
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')!).render(<App />);
Why Did You Renderの基本的な使い方を動かす
まずは最小構成で動かしてみましょう。以下のサンプルでは、ボタンを押すたびに親コンポーネントが再レンダリングされ、その際に子コンポーネントGreetingへ中身は同じでも参照が異なるオブジェクトを渡しています。Why Did You RenderをwhyDidYouRender(React, { trackAllPureComponents: true })で初期化しておくと、React.memoで囲んでいるにもかかわらず再レンダリングされてしまう様子を、コンソールで確認できます。
要点だけを取り出すと、Why Did You Renderのセットアップは次の3行に集約されます。whyDidYouRender()にReact本体を渡して監視を開始し、trackAllPureComponents: trueでアプリ内のmemoコンポーネントをまとめて追跡対象にしています。
import whyDidYouRender from '@welldone-software/why-did-you-render'
// Reactをモンキーパッチし、memo化されたコンポーネントを一括で監視する
whyDidYouRender(React, {
trackAllPureComponents: true,
})
const Greeting = React.memo(function Greeting({ user }) {
return <p>こんにちは、{user.name}さん</p>
})
実際にブラウザで動かして確認できるのが下のサンプルです。ボタンを押してGreetingが再レンダリングされる様子と、コンソールに出力される理由を見てみてください。
ボタンをクリックすると、コンソールにGreetingの再レンダリング理由が表示されます。userプロパティの中身(name: '太郎')は変わっていないのに、オブジェクトの参照だけが毎回変わっているため再レンダリングが発生している——これがWhy Did You Render(WDYR)の言う「避けられた再レンダリング」です。サンプル内のconst user = { name: '太郎' }をReact.useMemo(() => ({ name: '太郎' }), [])に書き換えると、参照が固定されてWDYRのログが出なくなることも確認できます。
実践的なユースケース
whyDidYouRenderで特定のコンポーネントだけを個別に追跡する
trackAllPureComponentsはアプリ全体のmemoコンポーネントを監視するため、大規模なアプリではログが大量に出てノイズになりがちです。調査対象を絞りたいときは、コンポーネントにwhyDidYouRender = trueを直接設定することで、そのコンポーネントだけをピンポイントで追跡できます。
個別追跡の要点は次のとおりです。whyDidYouRender(React)をtrackAllPureComponentsなしで呼び出し、監視したいコンポーネントにだけ静的プロパティwhyDidYouRender = trueを付けます。
import whyDidYouRender from '@welldone-software/why-did-you-render'
// trackAllPureComponentsは付けず、個別指定のみ有効にする
whyDidYouRender(React)
const Badge = React.memo(function Badge({ style }) {
return <span style={style}>New</span>
})
Badge.whyDidYouRender = true // このコンポーネントだけ追跡する
下のサンプルで、実際にBadgeだけがログに出る様子を確認できます。
Badgeだけがログに出て、同じくmemoで囲んだFooterは静かなままなのが確認できます。「怪しいコンポーネントにだけ目印を付けて調査する」という、実務でそのまま使える絞り込み方です。試しにサンプル内のFooterにもFooter.whyDidYouRender = trueを追加してみると、これまで静かだったFooterについても再レンダリング理由がログに出るようになり、whyDidYouRenderプロパティが追跡対象を決めていることが分かります。
notifierオプションでログの出力内容をカスタマイズする
デフォルトのログはやや情報量が多く、チームによっては「コンポーネント名と理由だけ簡潔に見たい」ということもあります。そんなときはnotifierオプションで、ログ出力そのものを自由に組み立て直せます。
notifierには再レンダリングのたびにコールバックが呼ばれ、対象のコンポーネントやprevProps・nextPropsなどが渡ってきます。以下は、そこからコンポーネント名だけを取り出して独自メッセージに整形する最小構成です。
whyDidYouRender(React, {
trackAllPureComponents: true,
notifier: (data) => {
const name = data.Component.displayName || data.Component.name
console.log(`[WDYR] ${name} が再レンダリングされました`)
},
})
実際の出力を確認できるサンプルが下です。
標準のログの代わりに、シンプルな1行メッセージが出力されるようになりました。notifierのコールバックにはdata.prevPropsとdata.nextPropsも渡ってくるため、たとえばnotifierの中身をconsole.log(name, data.prevProps, data.nextProps)に書き換えれば、どのプロパティが変化前後でどう変わったのかまで自前のフォーマットで出力できます。社内のロギング基盤に合わせてフォーマットを変えたり、特定条件のときだけ通知したりといった応用がしやすくなります。
まとめ
Why Did You Renderを使うと、「なんとなく重い」で終わっていた再レンダリングの調査を、コンソールに出る具体的な差分情報をもとに進められるようになります。今回紹介したように、
trackAllPureComponentsでアプリ全体を大まかに監視するwhyDidYouRender = trueで怪しいコンポーネントだけを個別に追跡するnotifierでログ出力を自分たちの運用に合わせてカスタマイズする
といった使い分けができるので、開発規模やチームの運用に応じて柔軟に導入できます。useMemoやuseCallbackを「なんとなく」付けるのではなく、実際に効果があるかを確認しながらチューニングしたいときにぜひ試してみてください。
