仕組みから学ぶ Web
発展読了目安 15 分#React#再描画#状態#メモ化#INP#パフォーマンス

再描画はなぜ起きるか — 1 文字打つたびに、表ぜんぶが描き直されていた

検索欄に 1 文字打つたびに、画面が 0.4 秒固まった。メモ化を全部の部品に付けても変わらなかった。描き直される範囲は、状態を持つ場所で決まる。

この記事の進み方

What — 何が、どこまで描き直されるか

React のような UI の仕組みでは、画面を部品(コンポーネント)の木として組みます。各部品は「状態を受け取って、画面の形を返す関数」です。

状態が変わると、次のことが起きます。

  1. その状態を持つ部品が、関数をもう一度実行する(これを再描画と呼ぶ)
  2. その部品が返す子の部品も、すべてもう一度実行される
  3. 前回の結果と比べ、違いがあった部分だけを実際の画面(DOM)に反映する

大事なのは、2 と 3 が別だという点です。画面に反映されるのは違いの分だけでも、関数の実行は子孫ぜんぶに及びます。5,000 行の表なら、5,000 回の関数の実行と比較が走ります。

Compare状態を置く場所で、描き直す範囲が変わる

検索の文字を、画面の親に持たせる。

画面(検索の文字を持つ)
├ 検索欄
├ 並べ替えボタン
└ 表
 └ 行 × 5,000

1 文字 → 画面から下を全部実行
1 文字で実行する部品
約 5,000
入力の遅れ
0.4 秒
書きやすさ
書きやすい
1 回で実行する行
—
作る手間
—
  • –状態を上に集めると、どこからでも使えて書きやすい。その代わり、変わるたびに下ぜんぶが動く。
  • –冒頭の事故はこれ。

同じ画面、同じ状態。置く場所だけが違う。

メモ化が効く条件

「子の部品は、渡された値が前回と同じなら、実行を飛ばす」。これがメモ化です。ただし「同じ」は、中身ではなく参照で比べます。

// 描画のたびに新しいオブジェクトと関数を作って渡している
<Row style={{ color: "gray" }} onClick={() => select(id)} />
// 中身が同じでも、毎回「別のもの」なので、メモ化は効かない

メモ化が効くのは、渡す値が本当に前回と同じもののときだけです。オブジェクトや関数を渡すなら、それ自体も同じものを使い回す必要があります。冒頭の担当者は、行をメモ化した一方で、渡す値を毎回作り直していました。

確認 — ここまで読めたか

画面の親の部品が状態を 1 つ持っています。その状態が変わったとき、関数がもう一度実行されるのはどこまでですか?

まず選ぶ(解答例は a〜d の記号で説明します)

Why — なぜ「全部メモ化」では直らないのか

状態を上に置くのは、書きやすいから

状態を画面のいちばん上に集めると、どの部品からも使えて、受け渡しに悩みません。書きやすさを得て、描き直す範囲の広さを払っています。部品が少ないうちは、この払うものは見えません。表が 5,000 行になったときに現れます。

メモ化は、範囲を狭める道具ではない

メモ化は、広い範囲が描き直される前提で、その中の一部を飛ばす道具です。状態が上にある限り、描き直しの呼びかけは表ぜんぶに届きます。メモ化はそれを 1 つずつ断っているだけで、しかも断るための比較にも費用がかかります。

範囲を狭めるのは、状態を使う場所の近くに置くことです。こちらは呼びかけ自体が届かなくなります。

測ってから直す

再描画は、見えないので推測で直しがちです。直す前に測ります。

  • 開発者ツールのプロファイラで、1 回の操作でどの部品が、何ミリ秒かけて実行されたかを見る
  • 利用者の側では、操作から画面の反応までの時間(Core Web Vitals の INP)で見る

このサイト自身も、図解の再描画を測ったうえで、メモ化を自動で入れる仕組みを入れないと決めています。1 回の操作が 0.2 ミリ秒で、削る余地が無かったためです。測らずに入れると、測れない差に費用を払います。

確認 — ここまで読めたか

行の部品をメモ化したのに、親が再描画されるたびに全行が再実行されます。最も疑わしい原因はどれですか?

まず選ぶ(解答例は a〜d の記号で説明します)

演習 — まず自分で判断する

解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。

演習 — 設計判断を問う

冒頭の顧客管理画面の遅さを、どの順で直しますか?

与えられた条件
  • 検索欄の文字は、画面いちばん上の部品の状態にある
  • 表は 5,000 行。1 行に 8 列。行とセルはすべてメモ化済みだが効いていない
  • 並べ替えのボタンと、選択中の行の数を示すバッジも、同じ親の状態を使っている
  • 来週までに、入力の遅れを体感できないところまで直したい
この軸で考える
  • · 描き直しの範囲を決めているのはどこか
  • · 今のメモ化が効いていない理由は何か
  • · 直す前に、何を測って効果を確かめるか
まず選ぶ(解答例は a〜d の記号で説明します)

演習 — 説明できるか

「再描画が遅いときは、とりあえず全部の部品をメモ化すればいい」と言う後輩に、それでは足りない理由を説明してください

与えられた条件
  • 相手はメモ化の書き方は知っているが、仕組みは理解していない
  • 相手のチームには、メモ化だらけで遅いままの画面がある
  • 何から見ればよいかまで示したい

読み終わりましたか?

読了にすると、これを前提とする記事がロードマップで開放されます。

この記事を前提にしている記事