こう 聞かれます
- 「React で
再レンダリングが 多くて 重い とき、 どう 直しますか」 - 「memo、
useMemo、 useCallback は、 いつ 使いますか」 - 「何千件も
ある 一覧が 重いと 言われました。 どうしますか」
面接官が 見ている点
- いきなり memo を
付けて 回らず、 まず 測って 原因の 部品を 特定する、と言えるか - 再レンダリングが
起きる 条件 (自分の state が 変わる、 親が 描き直される ) を 分かっているか - state を
使う 場所の 近く へ下げる、という 作りの 直しを 先に 考えられるか - 長い
一覧は、 見えている 分だけ描く (仮想スクロール) という 手を 知っているか
短い答え
- まず React DevTools の Profiler で、
どの 部品が 何度、 どれだけ 時間を かけて 描き直されているかを 測り、 原因を 特定します。 - 次に、
state を 使う 部品の 近くまで 下げて、 関係ない 部品が 巻き込まれないようにし、 それでも 重い所だけ memo、 useMemo、 useCallback を 使います。 - 何千件もの
一覧は、 画面に 見えている 分だけを 描く 仮想スクロールにします。 memo は 比べる 手間が 増えるので、 測って 効く 所にだけ付けます。
少し 詳しい答え
再レンダリングが 起きる条件
再レンダリングとは、
- その
部品の state が変わった - 親の
部品が 描き直された (子は、 props が 変わっていなくても、 既定では 一緒に 描き直されます) - 使っている Context の
値が変わった
描き直し自体は
まず測る
React DevTools の Profiler で
直す順番
いちばん効く
TypeScript (JSX)
// 直す前 入力の state を親が持つので、打つたびに一覧も描き直される
function Page() {
const [text, setText] = useState("");
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<BigList />
</>
);
}
// 直した後 入力の state を入力の部品に閉じ込める
function SearchBox() {
const [text, setText] = useState("");
return <input value={text} onChange={(e) => setText(e.target.value)} />;
}
function Page() {
return (
<>
<SearchBox />
<BigList />
</>
);
}それでも
- memo props が
前と 同じなら、 その 部品の 描き直しを 飛ばします - useMemo 重い
計算の 結果を、 依存配列の 値が 変わるまで 覚えておきます - useCallback 関数を
毎回作り直さず、 同じ物を 渡します。 memo を 付けた子に 関数を 渡すときに 意味があります
何千件もの
トレードオフ
memo や useMemo は、
仮想スクロールにも
話すときは、
よく ある間違い
- 測らずに memo を
全部に付ける 比べる 手間が 増え、 効かないこともあります。 Profiler で 原因を 見てから直します - state を
上の 部品に 置きすぎる 1 つの 入力の ために、 ページ全体が 描き直されます。 使う 部品の 近く へ下げます - useCallback だけ付けて、
子に memo が無い子は 親と 一緒に 描き直されるので、 関数を 同じに しても 効きません
深掘りの問い
- 「Context の
値が 変わると、 何が 描き直されますか」 その Context を 使う 部品が 全部描き直されるので、 変わる 頻度の 違う値は 別の Context に 分ける、と答えます - 「一覧の key に
配列の 番号を 使うと、 何が 困りますか」 並べ替えや 削除の ときに、 React が 別の 行を 同じ物と 思い込み、 入力の 中身が ずれたり余計に 描き直したりする、と答えます - 「描き直しの
回数より、 1 回が 重いときは」重い 計算を useMemo で 覚える、 サーバーで 先に 計算しておく、 画面に 出す量を 減らす、を挙げます