React Profilerの活用
この前、React レンダリング最適化(useMemo, useCallback, React.Memo)でも話したことはありますが、memo
, useMemo
,useCallback
の使用は性能の改善が必要と思う時に使うのが良いと述べました。そうしたら、改善が必要かどうかはどう判断したらいいでしょう?
React Developer Tools
ChromeでProfiler
を使用するために以下のリンクからReact Developer Tools
をインストールします。
Profiler
Profiler
を試すためにまず、5000枚のダミーイメージをレンダリングするコンポーネントを2つのバージョンに分けて作ってみます。
1つ目は下位コンポーネントに分けずに1個のコンポーネントで全てコンテンツをレンダリングする方法、
2つ目は下位コンポーネントに適切に分けてコンテンツをレンダリングする方法で作ってみます。
// src/components/PhotoListComponentV1.js
const PhotoListComponentV1 = ({ text, photoList }) => {
return (
<div>
<h1>PhotoListComponentV1</h1>
<p>text: {text}</p>
<ul>
{photoList.map((photo) => (
<li key={photo.id}>
<img src={photo.thumbnailUrl} alt={photo.title} />
</li>
))}
</ul>
</div>
);
};
export default PhotoListComponentV1;
// src/components/PhotoListComponentV2.js
const ShowText = ({ text }) => {
return <p>text: {text}</p>;
};
const PhotoItem = ({ photo }) => {
return (
<li>
<img src={photo.thumbnailUrl} alt={photo.title} />
</li>
);
};
const PhotoList = ({ photoList }) => {
return (
<ul>
{photoList.map((photo) => (
<PhotoItem key={photo.id} photo={photo} />
))}
</ul>
);
};
const PhotoListComponentV2 = ({ text, photoList }) => {
return (
<div>
<h1>PhotoListComponentV2</h1>
<ShowText text={text} />
<PhotoList photoList={photoList} />
</div>
);
};
export default PhotoListComponentV2;
// src/App.js
import { useState, useEffect } from "react";
import PhotoListComponentV1 from "./components/PhotoListComponentV1";
import PhotoListComponentV2 from "./components/PhotoListComponentV2";
const App = () => {
const [text, setText] = useState("");
const [photoList, setPhotoList] = useState([]);
// jsonplaceholderから5000枚のイメージデータをフェッチします。
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/photos")
.then((res) => res.json())
.then(setPhotoList);
}, []);
return (
<div style={{ margin: "32px", textAlign: "center" }}>
<div>
<label name="text">[text]</label>
<input value={text} onChange={(e) => setText(e.target.value)} />
</div>
<div style={{ display: "flex", justifyContent: "space-around" }}>
<PhotoListComponentV1 text={text} photoList={photoList} />
<PhotoListComponentV2 text={text} photoList={photoList} />
</div>
</div>
);
};
export default App;
サーバを起動し、ブラウザを開いてChromeのInspectモードからProfiler
タブに移動します。
Start profiling
ボタンを押下してレコーディングをスタートします。
input
に内容(1、2,3)を入力した後Stop profiling
を押下して結果を確認します。
Flamegraphチャート
を見るとApp Component
のレンダリングには184.4ms、その中でPhotoListComponentV1
は70.3ms、PhotoListComponentV2
は113.7msの時間かかったのが分かりました。
Rankedチャート
を見るとコンポーネント毎のレンダリング所要時間順に並んでいる結果も確認できます。
ここまで見るとPhotoListComponentV1
の方がPhotoListComponentV2
のよりパフォーマンスが良いようですね。
React.memo適用後
Profiler
タブでView settings
ボタンを押下して、Highlight updates when components render
項目にチェックします。
これからはコンポーネントがレンダリングされる時、どの部分がレンダリングされるのかが視覚的に表示されます。
input
に内容(1、2、3、4、5)を入力してみると入力する度に、input
コンポーネントだけレンダリングすればいいのにすべてのコンポーネントが再レンダリングしてしまうのが分かります。
React.memoを使ってPhotoListComponentV1
,PhotoListComponentV2
を最適化してみます。
// src/components/PhotoListComponentV1.js
import { memo } from "react";
const PhotoListComponentV1 = ({ text, photoList }) => {
return (
<div>
<h1>PhotoListComponentV1</h1>
<p>text: {text}</p>
<ul>
{photoList.map((photo) => (
<li key={photo.id}>
<img src={photo.thumbnailUrl} alt={photo.title} />
</li>
))}
</ul>
</div>
);
};
// export default PhotoListComponentV1;
export default memo(PhotoListComponentV1);
// src/components/PhotoListComponentV2.js
import { memo } from "react";
// const ShowText = ({ text }) => {
// return <p>text: {text}</p>;
// };
// const PhotoItem = ({ photo }) => {
// return (
// <li>
// <img src={photo.thumbnailUrl} alt={photo.title} />
// </li>
// );
// };
// const PhotoList = ({ photoList }) => {
// return (
// <ul>
// {photoList.map((photo) => (
// <PhotoItem key={photo.id} photo={photo} />
// ))}
// </ul>
// );
// };
// export default PhotoListComponentV2;
const ShowText = memo(({ text }) => {
return <p>text: {text}</p>;
});
const PhotoItem = memo(({ photo }) => {
return (
<li>
<img src={photo.thumbnailUrl} alt={photo.title} />
</li>
);
});
const PhotoList = memo(({ photoList }) => {
return (
<ul>
{photoList.map((photo) => (
<PhotoItem key={photo.id} photo={photo} />
))}
</ul>
);
});
const PhotoListComponentV2 = ({ text, photoList }) => {
return (
<div>
<h1>PhotoListComponentV2</h1>
<ShowText text={text} />
<PhotoList photoList={photoList} />
</div>
);
};
export default memo(PhotoListComponentV2);
またProfiler
を使った後、結果を確認すると、
レンダリング時間が69.7ms、PhotoListComponentV1
は69.3ms、PhotoListComponentV2
は0.2msで、PhotoListComponentV2
が大幅に改善できたのが分かります。input
が変わる時、input
コンポーネントだけ再レンダリングするためです。
終わりに
レンダリング最適化のためにはコンポーネントを適切に分けて使用し、memo
, useMemo
, useCallback
を使えば良いと思います。ただ、本当に最適化が必要なのか判断するためには今回ご紹介したProfiler
が役に立つかと思います。
Discussion