Upgrade to Pro — share decks privately, control downloads, hide ads and more …

アンチパターンを避ける型駆動React最適化

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for seriseri seriseri
May 31, 2026
11

 アンチパターンを避ける型駆動React最適化

Avatar for seriseri

seriseri

May 31, 2026

Transcript

  1. SECTION 00 本日お話しすること Agenda 01 イントロ なぜ今 React Compiler か

    / 手動メモ化の限界 02 コンパイラの仕組み AST + データフロー解析 / メモ化境界の決定 03 最適化を阻害するアンチパターン 副作用・ミュータブル・参照不安定 04 型駆動で防ぐ TypeScript で純粋性を表現する 05 Biome / Oxc で機械的に守る Lint と CI でのガードレール 06 大規模プロジェクトの新しい設計指針 再レンダ最適化から責務分離へ 02 / 24 PeopleX
  2. SECTION 01 手動メモ化のコスト なぜ「手動メモ化地獄」が生まれたのか TSX // “とりあえず全部メモ化”の典型 const Row =

    memo((props) => { const label = useMemo(() => formatLabel(props.user), [props.user]); const onClick = useCallback(() => { props.onSelect(props.user.id); }, [props.user.id, props.onSelect]); return <li onClick={onClick}>{label}</li>; }); 実装現場で起きていたこと 依存配列の管理コスト exhaustive-deps と毎回戦う / lint 抑制が常態化 メモ化漏れ・誤メモ化 新メンバーは正しく書けない / 直しても効かない レビューの大半が再レンダ議論 本質的なロジック議論にたどり着かない バンドルサイズと可読性の悪化 プロダクトコードがメモ化で埋め尽くされる 04 / 24 PeopleX
  3. SECTION 01 React Compiler の登場 React 19 + React Compiler

    が前提を変えた ✕ これまで 手動メモ化 useMemo useCallback React.memo → ◦ これから コンパイル時の自動メモ化 AST + データフロー解析で自動挿入 → ◦ そのために必要 純粋なコンポーネント 純粋なフック 副作用なし / ミュータブル操作なし React Compiler が行うこと ▪ コンポーネントとフックを AST に変換し、参照グラフ・依存関係を静的に解析 ▪ 純粋(同じ入力 → 同じ出力 / 副作用なし)な範囲を自動でメモ化境界として切り出し ▪ ランタイムでは React Forget スタイルのキャッシュにより props / state ごとに再計算を回避 ▪ 副作用 / ミュータブル操作を含むコードは “最適化されない”だけで、壊れはしない 05 / 24 PeopleX
  4. SECTION 01 設計の重心が動く 「再レンダ最適化」から「純粋性・副作用分離」へ BEFORE React 18 まで useMemo /

    useCallback / memo を“手で”張る 依存配列の正確さがチームに依存 再レンダリングが設計の中心トピック 純粋性は“あった方が良い”という温度感 AFTER React 19 + Compiler メモ化は基本コンパイラに任せる 純粋性・参照安定性が“仕様”になる 設計中心は責務分離と副作用の隔離 型 / Lint が最適化前提を守るゲート 06 / 24 PeopleX
  5. SECTION 02 コンパイラパイプライン React Compiler が行う5つのステップ 1 Parse JSX /

    TS → AST ソースから抽象構文木 へ › 2 HIR 中間表現の構築 制御フローと参照を抽 出 › 3 Dataflow データフロー解析 値の依存・到達定義を 追跡 › 4 Effect Infer 副作用 / 可変性の推 論 純粋性を満たす範囲を 特定 › 5 Memoize メモ化境界の挿入 キャッシュキー付きで再 書き出し ポイント:副作用や可変性が検出された箇所は、コンパイラが安全側に倒して最適化を諦める → つまり、最適化されるか否かは「純粋性が証明できるか」で決まる 08 / 24 PeopleX
  6. SECTION 02 データフロー解析 コンパイラは「何が変わると何が変わるか」を見ている TSX function UserCard({ user, theme })

    { const name = user.firstName + ' ' + user.lastName; const color = theme.primary; const style = { color }; return ( <Card style={style}>{name}</Card> ); } 解析結果(概念) name ← user.firstName, user.lastName color ← theme.primary style ← color JSX ← name, style → user / theme どちらかが参照等価で変わらなければ JSX 全体をキャッシュする 09 / 24 PeopleX
  7. SECTION 02 最適化される / されない 純粋に書けているかが判定基準 ◯ 最適化される TSX function

    Total({ items }) { const total = items.reduce( (a, b) => a + b.price, 0 ); return <p>{total}</p>; } 入力 items に純粋に依存。reduce は新規配列を作らず 読み取りのみ。 ✕ 最適化されない TSX function Total({ items }) { let total = 0; items.forEach(i => { total += i.price; // mutation }); logger.info(total); // side effect return <p>{total}</p>; } ローカル変数の再代入 + ログ出力。副作用と可変性で“諦 め”られる。 10 / 24 PeopleX
  8. SECTION 03 全体像 コンパイラに諦められる 4つのパターン A 副作用の混入 render 中の I/O・ログ・

    乱数・Date.now() B ミュータブル操作 let の再代入 / push / sort / 直接代入 C 参照不安定 毎回新規生成のオブ ジェクト / 関数を子に渡 す D 非決定的な依存 外部スコープのクロー ジャ越し可変参照 12 / 24 PeopleX
  9. SECTION 03 A: 副作用 / B: ミュータブル操作 render 中の I/O

    と let / push を見直す A. 副作用の混入 NG function Now() { const t = Date.now(); // NG return <p>{t}</p>; } OK function Now() { const t = useNow(); // OK return <p>{t}</p>; } B. ミュータブル操作 NG const sorted = items; sorted.sort((a, b) => a.id - b.id); // props.items を破壊 OK const sorted = [...items].sort( (a, b) => a.id - b.id ); 「render は値を返す関数」— 副作用は useEffect / イベントハンドラ / 専用フックに切り出す 13 / 24 PeopleX
  10. SECTION 03 C: 参照不安定 / D: 非決定的な依存 props 境界と外部参照の取り扱い C.

    参照不安定(境界を超える毎回 new) NG <Child options={{ size: 'lg' }} /> // 親が再レンダする度に新規オブジェクト OK const OPTIONS = { size: 'lg' } as const; <Child options={OPTIONS} /> // or: 親側で純粋に算出 → 自動メモ化が効く D. 非決定的な依存(外部の可変参照) NG let counter = 0; // module scope function Badge() { counter++; // NG: 外部可変参照 return <span>{counter}</…>; } OK function Badge() { const count = useCounter(); return <span>{count}</…>; } 「render は入力(props / state / context)だけに依存」 14 / 24 PeopleX
  11. SECTION 04 型による不変性の表明 Readonly でミュータブル操作を書けなくする TS // props を deep

    readonly で受ける type Props = { items: ReadonlyArray<Item>; user: Readonly<User>; }; function List({ items }: Props) { items.push(...) // ✕ コンパイルエラー const next = [...items, x]; // ◦ } 型で禁止すれば、Compiler の検出を待たない IDE で即座にフィードバック 書いた瞬間に赤線が出る .push / .sort などの破壊的 API を遮断 Array.prototype の禁則をビルド前に止める DeepReadonly ヘルパで構造全体に伝播 props / state ツリー全体を不変化 「何をしてはいけないか」がコードで読める ドキュメントを別に持たなくて済む 16 / 24 PeopleX
  12. SECTION 04 純粋関数を型で表現する Branded type と Pure<T> で純粋契約を型化 TS //

    純粋関数を branded type で表現 declare const PURE: unique symbol; type Pure<F> = F & { readonly [PURE]: true }; function asPure<F extends (...a: any[]) => any>(fn: F): Pure<F> { return fn as Pure<F>; } // コールバックは Pure を要求 → 副作用関数は渡せない type TableProps<T> = { rows: ReadonlyArray<T>; rowKey: Pure<(row: T) => string>; }; → rowKey に副作用付き関数を渡すと型エラー。 Compiler の前段で純粋契約を強制できる。 17 / 24 PeopleX
  13. SECTION 05 Lint で前提を守る Biome と Oxc で React Compiler

    の前提を強制 Biome Rust 製の高速 Lint / Formatter noParameterAssign — 引数の再代入を禁止 useReadonlyClassProperties — 不変表明を強制 noUselessFragments / noUselessRename — render を清潔に保つ react-compiler 連携で“最適化不可” warning を出力 Oxc Rust 製のオールインワン JS/TS ツールチェイン oxlint react/exhaustive-deps — 依存配列の網羅性 oxlint react-hooks/* — フック規約を高速検査 react_compiler 互換ルール — 副作用混入を検出 巨大コードベースでも秒オーダーで完走 → pre-commit / CI 向き 19 / 24 PeopleX
  14. SECTION 05 多層防御モデル 純粋性が壊れない仕組みを 4層で組む L1 型システム TypeScript / Readonly

    / Pure<T> コンパイル前に弾く L2 Linter Biome / Oxc / eslint-react-compiler 保存 / commit 前に弾く L3 React Compiler AST + データフロー解析 ビルド時に判定し最適化 L4 ランタイム React DevTools / Profiler 観測でリグレッション発見 20 / 24 PeopleX
  15. SECTION 06 コンパイラ時代の責務分離 純粋層 / 副作用層 / UI 層を物理的に分ける Effects

    I/O / fetch / storage / log 副作用は専用フック・サービスに隔離 Domain 純粋関数 / Pure<T> / 不変データ Compiler が最適化できる純粋層 UI JSX / Server Components 純粋層の値を“ただ描画する”だけ チームに残すルール 1 副作用は“フック / サービス”に追い出す render 中に I/O を呼ばない 2 ドメイン関数は Pure<T> を強制する TS 型で副作用を弾く 3 UI は純粋層の値を渡すだけ props は ReadonlyArray / Readonly 4 メモ化は書かない、書かせない lint で useMemo/useCallback を制限 22 / 24 PeopleX
  16. SECTION — まとめ まとめ 01 メモ化はコンパイラの仕事へ useMemo / useCallback /

    memo を貼るのは過去の作業 02 判定軸は「純粋性が証明できるか」 副作用 / 可変性 / 参照不安定があると最適化は諦められる 03 型と Lint で前提を守る Readonly / Pure<T> / Biome / Oxc で書けなくする 04 設計の重心は責務分離へ Effects / Domain / UI を物理的に分け、純粋層を厚くする 最適化されるコードは、結果として「読みやすく、テストしやすく、壊れにくい」コードである。 23 / 24 PeopleX