こんにちは、かつコーチです。
前回は「無限re-renderが起きる原因と対処法」を解説しました。
console.logを無限ループの中に埋め込んで原因を追う方法もありますが、正直かなり効率が悪いです。
今回は、そういったバグ調査を格段に楽にしてくれるReact DevToolsの使い方を紹介します。
「propsやstateの中身をその場で確認したい」「どのコンポーネントが再レンダリングされているか知りたい」という悩みを解決してくれる、Reactエンジニアの必須ツールです。
React DevToolsとは?
ブラウザに追加する拡張機能
React DevToolsは、ブラウザの開発者ツールにReact専用のタブを追加してくれる拡張機能です。
Chrome・Firefox・Edgeの拡張機能ストアから「React Developer Tools」を検索してインストールできます。
インストールが完了すると、開発者ツール(F12キーやCmd+Option+I)を開いたときに「Components」と「Profiler」という2つのタブが追加されます。
なぜ必要なのか
console.logでpropsやstateを確認する方法でも、デバッグ自体は可能です。
ただし、コンポーネントの数が増えてくると、どのログがどのコンポーネントのものか分かりにくくなりますし、確認するたびにコードを書き換えてリロードする手間もかかります。
React DevToolsを使えば、コードを1行も書き換えずに、動作中のアプリのコンポーネントツリーとその中身をリアルタイムで確認できます。
Componentsタブの使い方
コンポーネントツリーとpropsの確認
「Components」タブを開くと、画面に表示されているコンポーネントが親子関係のツリーとして表示されます。
function UserCard({ userId }: { userId: string }) {
const [user, setUser] = useState<{ name: string; age: number } | null>(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
if (!user) return <p>読み込み中...</p>;
return (
<div>
<p>{user.name}</p>
<p>{user.age}歳</p>
</div>
);
}
このコンポーネントをツリーから選択すると、右側のパネルにpropsとして渡されたuserIdの値や、hooksセクションにuseStateで管理しているuserの中身が表示されます。
「今このコンポーネントにどんな値が渡っているのか」「stateは今どうなっているのか」を、コードを書き換えずにその場で確認できるのが最大の利点です。
state・propsをその場で書き換えて動作確認する
Componentsタブでは、表示されているpropsやstateの値を直接クリックして書き換えることもできます。
たとえばuser.ageの値を一時的に0に変えて、「年齢が0のときの表示崩れがないか」をコードを書き換えずに確認する、といった使い方ができます。
本番のロジックには一切触れずに、様々なパターンの表示を素早く試せるのはとても便利です。
コンポーネントを検索する
コンポーネントの数が多い画面では、上部の検索アイコンからコンポーネント名を検索して、目的のコンポーネントに素早くジャンプできます。
UserCardのようにコンポーネント名で検索すれば、ツリーの中から該当箇所がハイライトされます。
つまずきやすいポイント:かつコーチが実際に助けられた話
「無駄な再レンダリング」に気づけなかった経験
以前、リストの1項目をクリックしただけなのに、画面全体がわずかにカクつくという症状に悩まされたことがあります。
console.logを各コンポーネントの先頭に仕込んで地道に追っていたのですが、コンポーネントの数が多く、どのログがどのタイミングのものか分からなくなり、原因の特定に時間がかかっていました。
そこで使ったのが、Componentsタブの右上にある歯車アイコンから設定できる「Highlight updates when components render」という機能です。
これをオンにすると、再レンダリングされたコンポーネントが画面上で一瞬色付きの枠で囲まれるようになります。
実際に試してみると、1項目をクリックしただけなのに、リスト全体(数十件)が毎回枠で囲まれて光っていることが一目でわかりました。
原因は、リストの各行に渡していたonClick関数を親コンポーネントの中で毎回新しく作っていたことで、React.memoでメモ化していたにもかかわらず、propsの参照が毎回変わって再レンダリングされてしまっていたことでした。
❌ Before:親コンポーネントで毎回新しい関数を作ってしまう
function ItemList({ items }: { items: { id: string; label: string }[] }) {
const [selectedId, setSelectedId] = useState<string | null>(null);
return (
<ul>
{items.map((item) => (
// レンダリングのたびに新しい関数が作られ、memo化した子が毎回再レンダリングされる
<MemoizedItem key={item.id} item={item} onSelect={() => setSelectedId(item.id)} />
))}
</ul>
);
}
✅ After:idを引数として渡し、関数自体をメモ化する
import { useCallback, useState } from "react";
function ItemList({ items }: { items: { id: string; label: string }[] }) {
const [selectedId, setSelectedId] = useState<string | null>(null);
const handleSelect = useCallback((id: string) => {
setSelectedId(id);
}, []);
return (
<ul>
{items.map((item) => (
<MemoizedItem key={item.id} item={item} onSelect={handleSelect} />
))}
</ul>
);
}
useCallbackで関数自体をメモ化し、クリックされたアイテムのidを引数として渡すように変更しました。
これによってonSelectは毎回同じ関数の参照になり、React.memoが効くようになって、選んだ行以外の再レンダリングが起きなくなりました。
このとき、Highlight updatesの機能がなければ「どのコンポーネントが不要に再レンダリングされているか」を見つけるのにもっと時間がかかっていたはずです。
Profilerタブで描画コストを計測する
記録を開始してインタラクションを実行する
「Profiler」タブでは、実際にどのコンポーネントの描画にどれくらいの時間がかかっているかを計測できます。
左上の丸いアイコンをクリックすると記録が始まり、その状態でアプリを操作し、再度クリックすると記録が止まります。
記録後は、操作の中で発生した各レンダリングが横棒グラフのような「コミット」として並び、それぞれのコミットでどのコンポーネントがどれくらいの時間をかけて描画されたかを確認できます。
遅いコンポーネントを特定する
コミットを選択すると、その回でレンダリングされたコンポーネントが色付きのブロックで表示され、色が濃い(時間がかかっている)ほど重い処理になっていることが視覚的にわかります。
「なんとなく重い気がする」という感覚を、実際の数値と場所に落とし込めるのがProfilerタブの強みです。
次回解説するパフォーマンスチューニングでも、この計測結果をもとに改善箇所を判断していきます。
まとめ
この記事のポイント
- React DevToolsはブラウザ拡張として導入でき、「Components」と「Profiler」の2タブが使える
- Componentsタブでは、コンポーネントツリーからpropsやstateの中身をその場で確認・書き換えできる
- 「Highlight updates when components render」で、不要な再レンダリングを視覚的に発見できる
- Profilerタブでは、各コンポーネントの描画コストを計測し、遅い箇所を特定できる
次に読むべき記事
次回は「Reactアプリのパフォーマンスチューニング入門」です。
今回紹介したDevToolsの計測結果をもとに、実際の改善手法を解説していきます。
→ 次の記事:Reactアプリのパフォーマンスチューニング入門