マウスを動かすと再描画する問題の修正
PCブラウザでもっと多く発生するイベントはMouseMoveやScrollであることが多いです。
そのため、これらのイベントを監視して更新処理をする時は慎重にならないといけません。
これらの頻度が高いイベントを軽減する方法としてdebounceやIntersection Observerなどの新しいAPIがあります。
- Debouncing and Throttling Explained Through Examples | CSS-Tricks
- 入力ハンドラのデバウンス | Web | Google Developers
そもそもの問題として、そのイベントで何も変化する必要がないならばその更新処理を止めるべきです。
観測
視聴ページで、マウスを移動をすると載せただけで更新されるコンポーネントが幾つかあることがわかりました。
facebook/react-devtoolsの"Highlight Updates"を使うことで更新されているコンポーネントが点滅します。
マウス移動で何か表示が変わっているのなら、それは意図した挙動なので問題ありません。 しかし、表示が変わっていないにもかかわらず更新処理(点滅)を行っているならばそれは無駄といえます。
why-did-you-updateを使うことで、無駄な更新処理なのかを確認してみました。 次のような感じでReactをラップすると、無駄な更新をコンソールに表示してくれます。
import * as React from "react";
if (process.env.NODE_ENV !== "production") {
const { whyDidYouUpdate } = require("why-did-you-update");
whyDidYouUpdate(React);
}
ここでの"無駄な更新"とは、全く同じ値(Props)なのに更新(render)されている処理のことを言います。
- why-did-you-update
- 無駄な更新をコンソールに出力してくれる
- たとえばあるアクションにおいて無駄なレンダリングが起きてるかを調べるときに使う
- react-addons-perf(React 15限定)
- “Wasted” time がwhy-did-you-updateと似た感じ
- 一覧できるので分かりやすい
- アプリを起動時や色んな操作をした結果の統計を見るのに使う
- react-performance
- React 16対応
- 開発中
why-did-you-updateを入れた状態でマウス移動を行う特定のコンポーネントがコンソールログに表示されました。
合わせてコメント欄周辺をマウス移動した時のプロファイルを取ってみます。

先ほどのwhy-did-you-updateとの出力と合わせてみると、Formコンポーネントの下にある、InputやButtonやFieldといったコンポーネントが無駄に更新されていることがわかります。
問題
次のコンポーネントが同じPropsを受け取ったにもかかわらず更新されているのが問題です。
InputButtonField
実装を見るとReact.Componentを継承していて、かつshouldComponentUpdateが実装されていませんでした。
export class Field extends React.Component {}
この場合はPropsが変わるたびに更新されます。
修正方針
shouldComponentUpdateを実装して、前回と同じPropsなら更新しないようにすれば解決できそうです。
Note
この問題の修正のために独自のDeepComparisonRenderingOptimizedComponentという親コンポーネントを使っています。(既存の構造の問題があったため)
通常のケースではReactが提供しているReact.PureComponentのShallow(浅い)な比較で十分でしょう。
Shallowな比較と、Deepな比較の違いについては次の記事で解説しています。
修正
該当するそれぞれのコンポーネントにshouldComponentUpdateを実装を追加しました。

計測
Before
マウスを動かすとwhy-did-you-updateのログがでています。

After
マウスを動かしても無駄な更新されるコンポーネントはなくなりました。 why-did-you-updateのログが出なくなった)
具体的には、Formまでupdate処理が止まっていることがプロファイルからわかります。
つまり、Form次のコンポーネントにおいてshouldComponentUpdateにより更新が不要と判断され、update処理が行われなくなっていることを意味します。
InputButtonField
