メインコンテンツまでスキップ

パフォーマンス最適化

この章では、Reactアプリケーションのパフォーマンスを最適化するための手法を学びます。

この章で学ぶこと

  • React Compiler による自動メモ化の仕組みと、手動メモ化との使い分け
  • 再レンダリングの仕組みとトリガー
  • React.memo、useMemo、useCallbackの使い分け(Compiler を使えない場合向け)
  • useTransitionとuseDeferredValueによるUI応答性向上
  • コード分割とリストの仮想化
備考

この章では03章で作成したプロジェクトを使用します。npm run devで開発サーバーを起動し、コードを書きながら学習を進めましょう。

備考

パフォーマンス最適化は「問題が発生してから」対応するのが基本です。「推測ではなく計測」に基づいて最適化しましょう。Reactはデフォルトで十分高速であり、ほとんどのケースで最適化は不要です。最適化のコードは複雑さを増すため、本当に必要な場合にのみ適用してください。

React Compiler による自動メモ化

React Compiler は、ビルド時にコードを解析して必要な箇所だけを自動的にメモ化するコンパイラです。2025年10月に v1.0 がリリースされ、Next.js 16 では stable 扱いになりました(ただしデフォルトは無効で、設定で明示的に有効化します)。

React Compiler が自動で行うこと
├─ 変更されない値のメモ化(useMemo相当)
├─ 変更されない関数のメモ化(useCallback相当)
└─ 不要な再レンダリングのスキップ(React.memo相当)

つまり、React Compiler を有効にすれば、新規プロジェクトでは useMemo / useCallback / React.memo を手動で書く必要はほぼなくなります

備考

React Compiler が有効な場合、この章の後半で解説する React.memo / useMemo / useCallback原則不要 です。以下の例外ケースでのみ手動メモ化を検討してください。

  • サードパーティライブラリが参照等価性(===)に依存している
  • Compiler を使えない環境(古いビルドツール、React 18 以下)
  • プロファイラで計測した結果、Compiler の自動メモ化だけでは不十分と確認できた

React Compiler を有効にする

Vite + React の構成では、@rolldown/plugin-babel 経由で Babel プラグイン babel-plugin-react-compiler を組み込みます。Next.js 16 以降は next.config.jsreactCompiler: true で有効化します。

npm install -D babel-plugin-react-compiler @rolldown/plugin-babel @babel/core @types/babel__core
// vite.config.ts
import { defineConfig } from 'vite'
import react, { reactCompilerPreset } from '@vitejs/plugin-react'
import babel from '@rolldown/plugin-babel'

export default defineConfig({
plugins: [
react(),
babel({ presets: [reactCompilerPreset()] }),
],
})

なお、react({ babel: { plugins: [...] } }) のように babel オプションへ渡す形式は @vitejs/plugin-react 5 以前(Vite 7 まで)の旧設定です。v6.0.0 で babel オプションが削除されたため、現在は上記の構成を使います。

備考

create-vite 9 には「TypeScript + React Compiler」テンプレートが用意されています。新規プロジェクトなら scaffold 時にこのテンプレートを選ぶだけで React Compiler が有効になります。

備考

React Compiler は「Rules of React(React のルール)」に沿ったコードだけを最適化します。副作用を含む関数や、フックのルールに違反するコードはスキップされるため、安全に導入できます。ESLint プラグイン eslint-plugin-react-hooks(v6 以降に Compiler のルールが統合されています)の recommended-latest 設定で違反を検出できます。

本章の読み方

以降で解説する React.memo / useMemo / useCallback は、Compiler を使わない前提での最適化手法として位置づけてください。Compiler が有効な環境では、同じ効果が自動的に得られます。内部の仕組みを理解する教材として読むと、Compiler が何をしているかの理解にも役立ちます。

再レンダリングの仕組み

まず、Reactの再レンダリングがいつ発生するかを理解しましょう。

再レンダリングのトリガー

  1. Stateの変更
  2. Propsの変更
  3. 親コンポーネントの再レンダリング
  4. Contextの値の変更
function Parent() {
const [count, setCount] = useState(0)

console.log('Parent rendered')

return (
<div>
<button onClick={() => setCount(c => c + 1)}>
{count}
</button>
<Child /> {/* Parentが再レンダリングされると、Childも再レンダリング */}
</div>
)
}

function Child() {
console.log('Child rendered')
return <p>子コンポーネント</p>
}

React.memo

React.memoは、Propsが変更されない限り再レンダリングをスキップする高階コンポーネントです。

基本的な使い方

import { memo, useState } from 'react'

type ExpensiveComponentProps = {
data: string
}

const ExpensiveComponent = memo(function ExpensiveComponent({ data }: ExpensiveComponentProps) {
console.log('ExpensiveComponent rendered')
// 重い処理...
return <div>{data}</div>
})

// 使用例
function Parent() {
const [count, setCount] = useState(0)
const [text, setText] = useState('Hello')

return (
<div>
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
<ExpensiveComponent data={text} />
{/* textが変わらない限り、ExpensiveComponentは再レンダリングされない */}
</div>
)
}

カスタム比較関数

デフォルトでは浅い比較が行われます。カスタム比較が必要な場合は第2引数で指定します。

type UserProps = {
user: {
id: number
name: string
email: string
}
}

const UserCard = memo(
function UserCard({ user }: UserProps) {
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
)
},
(prevProps, nextProps) => {
// trueを返すと再レンダリングをスキップ
return prevProps.user.id === nextProps.user.id
}
)
警告

カスタム比較関数の過度な使用は避けてください。ほとんどの場合、データ構造を見直すか、後述のuseMemoを使う方が適切です。

useMemo

useMemoは、計算結果をメモ化し、依存配列が変更されるまで再計算を防ぎます。

高コストな計算のメモ化

import { useMemo, useState } from 'react'

function ProductList({ products }: { products: Product[] }) {
const [filter, setFilter] = useState('')
const [sortBy, setSortBy] = useState<'name' | 'price'>('name')

// filterとsortByが変わらない限り、再計算されない
const filteredAndSortedProducts = useMemo(() => {
console.log('Filtering and sorting...')

return products
.filter(p => p.name.toLowerCase().includes(filter.toLowerCase()))
.sort((a, b) => {
if (sortBy === 'name') {
return a.name.localeCompare(b.name)
}
return a.price - b.price
})
}, [products, filter, sortBy])

return (
<div>
<input
value={filter}
onChange={e => setFilter(e.target.value)}
placeholder="検索..."
/>
<select value={sortBy} onChange={e => setSortBy(e.target.value as 'name' | 'price')}>
<option value="name">名前順</option>
<option value="price">価格順</option>
</select>

<ul>
{filteredAndSortedProducts.map(product => (
<li key={product.id}>{product.name}: ¥{product.price}</li>
))}
</ul>
</div>
)
}

オブジェクト参照の安定化

React.memoと組み合わせる場合、オブジェクトの参照を安定させるために使用します。

function Parent() {
const [count, setCount] = useState(0)

// 毎回新しいオブジェクトが作成される → Childが再レンダリング
// const config = { theme: 'dark', size: 'large' }

// useMemoで参照を安定化
const config = useMemo(() => ({
theme: 'dark',
size: 'large'
}), []) // 空の依存配列 = 初回のみ作成

return (
<div>
<button onClick={() => setCount(c => c + 1)}>{count}</button>
<MemoizedChild config={config} />
</div>
)
}

const MemoizedChild = memo(function Child({ config }: { config: Config }) {
console.log('Child rendered')
return <div>{config.theme}</div>
})

useCallback

useCallbackは、関数をメモ化し、依存配列が変更されるまで同じ関数参照を保持します。

基本的な使い方

import { useCallback, useState, memo } from 'react'

function Parent() {
const [count, setCount] = useState(0)
const [text, setText] = useState('')

// 毎回新しい関数が作成される
// const handleClick = () => console.log('clicked')

// useCallbackで関数をメモ化
const handleClick = useCallback(() => {
console.log('clicked')
}, []) // 依存なし

const handleTextChange = useCallback((newText: string) => {
setText(newText)
}, []) // setTextは安定した参照なので依存不要

return (
<div>
<button onClick={() => setCount(c => c + 1)}>{count}</button>
<MemoizedButton onClick={handleClick} />
<MemoizedInput onChange={handleTextChange} value={text} />
</div>
)
}

const MemoizedButton = memo(function Button({ onClick }: { onClick: () => void }) {
console.log('Button rendered')
return <button onClick={onClick}>クリック</button>
})

const MemoizedInput = memo(function Input({
value,
onChange
}: {
value: string
onChange: (value: string) => void
}) {
console.log('Input rendered')
return (
<input
value={value}
onChange={e => onChange(e.target.value)}
/>
)
})

依存配列の注意点

function SearchForm({ onSearch }: { onSearch: (query: string) => void }) {
const [query, setQuery] = useState('')

// onSearchが依存配列にないと、古いonSearchを使い続ける可能性
const handleSubmit = useCallback((e: React.FormEvent) => {
e.preventDefault()
onSearch(query)
}, [query, onSearch])

return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button type="submit">検索</button>
</form>
)
}

最適化が不要なケース

警告

過度な最適化は、かえってコードを複雑にし、バグの原因になります。React.memouseMemouseCallbackを「念のため」使うのは避けてください。メモ化自体にもコストがあり、不適切に使用するとパフォーマンスが悪化することもあります。

メモ化が不要なケース

// ❌ 不要: 単純なコンポーネント
const SimpleText = memo(({ text }: { text: string }) => {
return <span>{text}</span>
})

// ❌ 不要: 軽量な計算
const total = useMemo(() => items.length, [items])

// ❌ 不要: memo化していない子・DOM要素に渡すだけの関数
const handleClick = useCallback(() => {
setCount(c => c + 1)
}, [])
// → 渡す先がmemo化されていなければ、参照を安定させても再レンダリングは減らない
// (setCountが安定なので依存配列は空にできるが、それはuseCallbackを使う理由にはならない)

メモ化が必要なケース

// ✅ 必要: 高コストな計算
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => complexCompare(a, b))
}, [items])

// ✅ 必要: memo化されたコンポーネントに渡すオブジェクト/関数
const config = useMemo(() => ({ theme, size }), [theme, size])
const handleChange = useCallback((value) => onChange(value), [onChange])

// ✅ 必要: useEffectの依存配列に含まれるオブジェクト/関数
const options = useMemo(() => ({ limit: 10 }), [])
useEffect(() => {
fetchData(options)
}, [options])

useTransition

Concurrent Renderingとは

React 18で導入された「Concurrent Rendering(並行レンダリング)」は、Reactがレンダリング処理を中断・再開できる仕組みです。これにより、重い処理中でもユーザー入力に素早く応答できます。

useTransitionuseDeferredValueは、このConcurrent Renderingを活用するためのフックです。低優先度の更新を「遅延可能」としてマークし、高優先度の更新(ユーザー入力など)を優先的に処理します。

useTransitionは、UIをブロックせずに状態を更新するためのフックです。React 18で導入されました。

基本的な使い方

import { useState, useTransition, useMemo } from 'react'

function SearchResults() {
const [query, setQuery] = useState('')
const [searchQuery, setSearchQuery] = useState('')
const [isPending, startTransition] = useTransition()

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setQuery(value) // 即座に更新(高優先度)

startTransition(() => {
// この更新が引き起こす再レンダリングが低優先度(中断可能)になる
setSearchQuery(value)
})
}

// 重いフィルタリングはレンダー中に実行(searchQueryが変わったときだけ再計算)
const filteredItems = useMemo(() => {
return allItems.filter(item =>
item.toLowerCase().includes(searchQuery.toLowerCase())
)
}, [searchQuery])

return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <span>検索中...</span>}
<ul>
{filteredItems.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
)
}

タブ切り替えの例

function Tabs() {
const [tab, setTab] = useState('home')
const [isPending, startTransition] = useTransition()

const handleTabChange = (newTab: string) => {
startTransition(() => {
setTab(newTab)
})
}

return (
<div>
<nav>
<button onClick={() => handleTabChange('home')}>ホーム</button>
<button onClick={() => handleTabChange('posts')}>投稿</button>
<button onClick={() => handleTabChange('settings')}>設定</button>
</nav>

<div style={{ opacity: isPending ? 0.7 : 1 }}>
{tab === 'home' && <HomePage />}
{tab === 'posts' && <PostsPage />} {/* 重いコンポーネント */}
{tab === 'settings' && <SettingsPage />}
</div>
</div>
)
}

useDeferredValue

useDeferredValueは、値の更新を遅延させるフックです。

import { useDeferredValue, useState, useMemo } from 'react'

function SearchList({ items }: { items: string[] }) {
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)

// deferredQueryを使うことで、入力はスムーズに、リストは遅延更新
const filteredItems = useMemo(() => {
return items.filter(item =>
item.toLowerCase().includes(deferredQuery.toLowerCase())
)
}, [items, deferredQuery])

const isStale = query !== deferredQuery

return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="検索..."
/>
<ul style={{ opacity: isStale ? 0.7 : 1 }}>
{filteredItems.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
)
}

リストの最適化

大量のリストを表示する場合の最適化手法です。

keyの適切な使用

// ❌ インデックスをkeyに使用(並び替え時に問題)
{items.map((item, index) => (
<Item key={index} data={item} />
))}

// ✅ ユニークなIDをkeyに使用
{items.map(item => (
<Item key={item.id} data={item} />
))}

仮想化(Virtualization)

大量のアイテムを持つリストには、仮想化ライブラリを使用します。

// @tanstack/react-virtual を使用
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef } from 'react'

function VirtualList({ items }: { items: string[] }) {
const parentRef = useRef<HTMLDivElement>(null)

const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // 各アイテムの推定高さ
})

return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative'
}}
>
{virtualizer.getVirtualItems().map(virtualItem => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`
}}
>
{items[virtualItem.index]}
</div>
))}
</div>
</div>
)
}

コード分割

大きなバンドルを分割し、必要な部分だけを読み込みます。

React.lazy

import { lazy, Suspense, useState } from 'react'

// 動的インポート
const HeavyComponent = lazy(() => import('./HeavyComponent'))
const AdminPanel = lazy(() => import('./AdminPanel'))

function App() {
const [showAdmin, setShowAdmin] = useState(false)

return (
<div>
<Suspense fallback={<div>読み込み中...</div>}>
<HeavyComponent />
</Suspense>

<button onClick={() => setShowAdmin(true)}>
管理画面を開く
</button>

{showAdmin && (
<Suspense fallback={<div>読み込み中...</div>}>
<AdminPanel />
</Suspense>
)}
</div>
)
}

ルートベースの分割

import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router'

const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const Dashboard = lazy(() => import('./pages/Dashboard'))

function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
)
}

パフォーマンス計測

React Developer Tools Profiler

React Developer Toolsの「Profiler」タブで、レンダリング時間を計測できます。

  1. DevToolsを開き、「Profiler」タブを選択
  2. ⚙️(Settings)アイコンを開き、「Record why each component rendered while profiling」にチェックを入れる
  3. 「Record」ボタンをクリック
  4. アプリを操作
  5. 「Stop」をクリックして結果を確認

「Why did this render?」の読み方

記録後に各コンポーネントをクリックすると、右側のパネルに再レンダリングの原因が表示されます。よく見る原因と対応は次の通りです。

原因表示例対応
親の再レンダリングThis component rendered because its parent re-rendered親の再レンダリングが妥当か確認。不要なら React.memo / React Compiler の導入を検討
Props の変化(値が変わっていないのに毎回新しい参照)Props changed: (onClick)親で関数や配列を毎回作り直していないか確認。Compiler を有効化するか useCallback / useMemo で参照を安定化
State の変化Hook 1 changed実際に必要な更新か確認。無駄な setState は避ける
Context の変化Context changedContext を分割する(認証情報とテーマを別 Context に分ける等)。条件付きでしか使わない値は use(Context) を条件分岐内で呼び、不要なレンダーでの購読を避ける
備考

重要: 手動で最適化する前に React Compiler を有効化してください。Compiler が有効な環境では、Props changed の多くが自動解決されます。Profiler で問題を確認するのは、Compiler 導入後も改善しきれなかった箇所です。

Flamegraph と Ranked の使い分け

Profiler の結果は 2 種類のビューで確認できます。

  • Flamegraph: コンポーネントツリー構造のまま、描画時間を横幅で表示。どこが重いかの全体像を把握するのに向く
  • Ranked: 描画時間が長い順に並べる。具体的にどのコンポーネントを最適化すべきかを特定するのに向く

多くの場合は Ranked で重いコンポーネントを特定し、Flamegraph でツリー上の位置関係を確認する流れになります。

why-did-you-render

不要な再レンダリングの検出は、React DevTools Profiler の「Record why each component rendered while profiling」で同等の情報が得られます。サードパーティの why-did-you-render は webpack/CRA 時代の定番ライブラリですが、Vite + React 19 の構成では Babel の配線が別途必要になるため、本書では扱いません(導入する場合は公式 README を参照してください)。

最適化チェックリスト

  1. 本当に最適化が必要か? - 問題が発生してから対応
  2. React Developer Toolsで計測 - 推測ではなく計測に基づく
  3. 不要な再レンダリングを特定 - Profilerで確認
  4. React Compiler を有効化できないか確認 - 有効なら手動メモ化の大半は不要
  5. React.memoを適切に使用 - 高コストなコンポーネントのみ
  6. useMemo/useCallbackを適切に使用 - 依存配列を正しく設定
  7. 大量リストは仮想化 - 1000件以上なら検討
  8. コード分割 - 初期バンドルを小さく

試してみよう

以下の課題に挑戦して、パフォーマンス最適化の理解を深めましょう。

  1. React Developer Tools Profilerを使う: 03章のプロジェクトでProfilerタブを開き、ボタンをクリックしたときにどのコンポーネントが再レンダリングされるか確認してみましょう。「Highlight updates when components render」オプションを有効にすると、視覚的に確認できます。

  2. useMemoで計算をメモ化する: 1000件のリストをフィルタリング・ソートするコンポーネントを作成し、useMemoを使わない場合と使った場合で、console.logの出力回数を比較してみましょう。

  3. useTransitionでUI応答性を改善する: 検索フォームを作成し、入力中にリストをフィルタリングします。useTransitionを使って、入力の応答性を保ちながらリストを更新してみましょう。isPendingを使って「検索中...」の表示も追加してください。

ヒント
  1. DevToolsのProfilerタブで「Record」→アプリ操作→「Stop」の順で記録します。「Settings」から「Highlight updates」を有効化できます。
  2. useMemoの第2引数(依存配列)に、フィルタ条件とソート条件を含めることを忘れずに。
  3. startTransitionで囲むのはリストの状態更新部分です。入力値の更新はstartTransitionの外に置きます。
回答と解説

課題1: React Developer Tools Profilerを使う

React Developer Toolsをインストール後、DevToolsを開き「Profiler」タブを選択します。

  1. 歯車アイコン(設定)をクリック
  2. 「General」タブで「Highlight updates when components render」にチェック
  3. 「Record」ボタンをクリックして記録開始
  4. アプリ内でボタンをクリックするなど操作する
  5. 「Stop」ボタンで記録終了

記録結果では、各コンポーネントのレンダリング時間と「Why did this render?」(なぜ再レンダリングされたか)が確認できます。親コンポーネントの状態が変わると、子コンポーネントも再レンダリングされることが視覚的にわかります。


課題2: useMemoで計算をメモ化する

import { useState, useMemo } from 'react'

// 1000件のダミーデータ
const items = Array.from({ length: 1000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
category: ['A', 'B', 'C'][i % 3]
}))

function ItemList() {
const [filter, setFilter] = useState('')
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc')
const [count, setCount] = useState(0)

// useMemoなしの場合:countが変わるたびに再計算される
// const filteredItems = items
// .filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
// .sort((a, b) => sortOrder === 'asc'
// ? a.name.localeCompare(b.name)
// : b.name.localeCompare(a.name))

// useMemoありの場合:filter/sortOrderが変わったときだけ再計算
const filteredItems = useMemo(() => {
console.log('フィルタリング・ソート実行')
return items
.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
.sort((a, b) => sortOrder === 'asc'
? a.name.localeCompare(b.name)
: b.name.localeCompare(a.name))
}, [filter, sortOrder])

return (
<div>
<input
value={filter}
onChange={e => setFilter(e.target.value)}
placeholder="検索..."
/>
<button onClick={() => setSortOrder(s => s === 'asc' ? 'desc' : 'asc')}>
{sortOrder === 'asc' ? '昇順' : '降順'}
</button>
<button onClick={() => setCount(c => c + 1)}>
カウント: {count}
</button>
<ul>
{filteredItems.slice(0, 20).map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
)
}

useMemoなしの場合、カウントボタンを押すたびにconsole.logが出力されます。useMemoありの場合、カウントボタンを押してもconsole.logは出力されず、フィルタ条件やソート順を変えたときだけ再計算されます。


課題3: useTransitionでUI応答性を改善する

import { useState, useTransition, useMemo } from 'react'

// 大量のダミーデータ
const allItems = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`)

function SearchWithTransition() {
const [query, setQuery] = useState('')
const [searchQuery, setSearchQuery] = useState('')
const [isPending, startTransition] = useTransition()

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setQuery(value) // 入力は即座に反映(高優先度)

startTransition(() => {
// リストのフィルタリングは低優先度
setSearchQuery(value)
})
}

const filteredItems = useMemo(() => {
return allItems.filter(item =>
item.toLowerCase().includes(searchQuery.toLowerCase())
)
}, [searchQuery])

return (
<div>
<input
value={query}
onChange={handleChange}
placeholder="検索..."
className="px-3 py-2 border rounded"
/>
{isPending && <span className="ml-2 text-gray-500">検索中...</span>}

<ul className="mt-4" style={{ opacity: isPending ? 0.7 : 1 }}>
{filteredItems.slice(0, 100).map((item, index) => (
<li key={index} className="py-1">{item}</li>
))}
</ul>
<p className="text-sm text-gray-500">
{filteredItems.length}件中 {Math.min(100, filteredItems.length)}件表示
</p>
</div>
)
}

useTransitionを使うことで、入力フィールドへのタイピングは即座に反映され、重いリストのフィルタリングは低優先度で処理されます。isPendingtrueの間は「検索中...」と表示され、リストの透明度を下げることでユーザーに処理中であることを伝えます。

まとめ

  • Reactの再レンダリングの仕組みを理解する
  • React Compilerが有効ならuseMemo/useCallback/React.memoの大半は自動化される。手動メモ化はCompilerを使えない場合の補完
  • React.memoで不要な再レンダリングを防ぐ
  • useMemoで高コストな計算をメモ化
  • useCallbackで関数参照を安定化
  • useTransition/useDeferredValueでUIの応答性を向上
  • 過度な最適化は避け、計測に基づいて対応
  • React Developer Tools Profilerで計測

次の章では、デバッグ手法とReact Developer Toolsについて詳しく学びます。

次に読む