デザインについての学習メモブログ

React入門 #17 – パフォーマンス最適化の基本(React.memo, useMemo, useCallback)

記事内に広告が含まれています。

React入門 #17 – パフォーマンス最適化の基本(React.memo, useMemo, useCallback)

Reactアプリケーションが大きくなるにつれて、パフォーマンスが重要になります。

不要な再レンダリングを防ぐことで、アプリケーションを高速化できます。

この記事では、React.memo、useMemo、useCallbackを使った最適化の方法を詳しく学んでいきます。

Reactの再レンダリング

このセクションでは、Reactがいつ・なぜ再レンダリングを行うかの仕組みを理解できます。

「なんとなく動いている」状態から脱却し、パフォーマンス問題の原因を自分で特定できるようになります。

再レンダリングが起きるタイミング

Reactコンポーネントは、以下の4つのタイミングで再レンダリングされます。

これを把握しておくことが、最適化の第一歩です。

  1. stateが変更されたとき
  2. propsが変更されたとき
  3. 親コンポーネントが再レンダリングされたとき
  4. Contextの値が変更されたとき

問題のある例

親コンポーネントのstateが変化すると、関係のない子コンポーネントも一緒に再レンダリングされてしまいます

以下はその典型例です。

テキスト入力のたびに重い処理を持つExpensiveComponentが動いてしまうため、UIが詰まる原因になります。

JSX
import { useState } from 'react'

// 重い計算をするコンポーネント
function ExpensiveComponent({ value }) {
  console.log('ExpensiveComponentが再レンダリング')
  
  // 重い計算(デモ用)
  let result = 0
  for (let i = 0; i < 1000000000; i++) {
    result += i
  }
  
  return <div>値: {value}</div>
}

function App() {
  const [count, setCount] = useState(0)
  const [text, setText] = useState('')
  
  return (
    <div>
      {/* textが変わるたびにExpensiveComponentも再レンダリング */}
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={() => setCount(count + 1)}>カウント: {count}</button>
      <ExpensiveComponent value={count} />
    </div>
  )
}

この問題を最適化していきます。

React.memo・・・コンポーネント単位で再レンダリングをスキップ

コンポーネント単位で再レンダリングをスキップする方法を習得できます。

このセクションでは、React.memoの基本的な使い方から、カスタム比較関数を使った応用パターンまで身につきます。

基本的な使い方

React.memoコンポーネントをラップすると、propsが前回と同じ場合に再レンダリングをスキップします。

先ほどの問題例では、textが変化してもExpensiveComponentのprops(value)は変わっていないため、再レンダリングを防げます。

JavaScript版:

JSX
import { memo } from 'react'

// memoでラップ
const ExpensiveComponent = memo(function ExpensiveComponent({ value }) {
  console.log('ExpensiveComponentが再レンダリング')
  
  let result = 0
  for (let i = 0; i < 1000000000; i++) {
    result += i
  }
  
  return <div>値: {value}</div>
})

function App() {
  const [count, setCount] = useState(0)
  const [text, setText] = useState('')
  
  return (
    <div>
      {/* textが変わってもExpensiveComponentは再レンダリングされない */}
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={() => setCount(count + 1)}>カウント: {count}</button>
      <ExpensiveComponent value={count} />
    </div>
  )
}

TypeScript版:

TSX
import { memo } from 'react'

interface ExpensiveComponentProps {
  value: number
}

const ExpensiveComponent = memo(function ExpensiveComponent({ 
  value 
}: ExpensiveComponentProps): JSX.Element {
  console.log('ExpensiveComponentが再レンダリング')
  
  let result = 0
  for (let i = 0; i < 1000000000; i++) {
    result += i
  }
  
  return <div>値: {value}</div>
})

カスタム比較関数

デフォルトのReact.memoはpropsを浅い比較(shallow compare)で判定します。

「idが同じならオブジェクトの他のプロパティが変わっても再レンダリングしない」といった独自ルールを設けたい場合は、第2引数にカスタム比較関数を渡します。

trueを返すとスキップ、falseを返すと再レンダリングが実行されます。

JavaScript版:

JSX
import { memo } from 'react'

const UserCard = memo(
  function UserCard({ user }) {
    console.log('UserCardが再レンダリング')
    return (
      <div>
        <h3>{user.name}</h3>
        <p>{user.email}</p>
      </div>
    )
  },
  // カスタム比較関数
  (prevProps, nextProps) => {
    // trueを返すと再レンダリングをスキップ
    return prevProps.user.id === nextProps.user.id
  }
)

TypeScript版:

TSX
import { memo } from 'react'

interface User {
  id: number
  name: string
  email: string
}

interface UserCardProps {
  user: User
}

const UserCard = memo<UserCardProps>(
  function UserCard({ user }) {
    console.log('UserCardが再レンダリング')
    return (
      <div>
        <h3>{user.name}</h3>
        <p>{user.email}</p>
      </div>
    )
  },
  (prevProps, nextProps) => {
    return prevProps.user.id === nextProps.user.id
  }
)

useMemo・・・計算結果をキャッシュし不要な再計算を防ぐ

計算結果をキャッシュして、不要な再計算を防ぐ方法を習得できます。

単純な合計計算から、フィルタリング・ソートといった実務頻出の処理まで、useMemoの効果的な使い所を理解できます。

重い計算をメモ化

useMemo計算結果をキャッシュするフックです。

依存配列に指定した値が変わったときだけ再計算し、それ以外の再レンダリングではキャッシュされた値を返します。

コンポーネント内で時間のかかる処理(大量データの集計・変換など)を行う場合に有効です。

JavaScript版:

JSX
import { useState, useMemo } from 'react'

function ExpensiveCalculation({ numbers }) {
  // numbersが変わったときだけ再計算
  const sum = useMemo(() => {
    console.log('計算中...')
    return numbers.reduce((acc, num) => acc + num, 0)
  }, [numbers])
  
  return <div>合計: {sum}</div>
}

function App() {
  const [numbers] = useState([1, 2, 3, 4, 5])
  const [count, setCount] = useState(0)
  
  return (
    <div>
      {/* countが変わってもsumは再計算されない */}
      <button onClick={() => setCount(count + 1)}>
        カウント: {count}
      </button>
      <ExpensiveCalculation numbers={numbers} />
    </div>
  )
}

TypeScript版:

TSX
import { useState, useMemo } from 'react'

interface ExpensiveCalculationProps {
  numbers: number[]
}

function ExpensiveCalculation({ numbers }: ExpensiveCalculationProps): JSX.Element {
  const sum = useMemo(() => {
    console.log('計算中...')
    return numbers.reduce((acc, num) => acc + num, 0)
  }, [numbers])
  
  return <div>合計: {sum}</div>
}

memo(React.memo)
コンポーネント丸ごとの再レンダリングをスキップします。

useMemo
コンポーネントは再レンダリングされるけれど、その中のuseMemoでラップした特定の計算だけをスキップします。コンポーネント関数は呼ばれるが、依存配列が変わっていなければキャッシュした値をそのまま返します。

フィルタリングとソートの最適化

Todoリストなどで「絞り込み+並び替え」を同時に行う場面は非常によくあります。

これをコンポーネント内にそのまま書くと、無関係なstate変更のたびに毎回実行されてしまいます。

useMemoでまとめてメモ化することで、todosfiltersortByが変わったときだけ再計算されるようになります。

JavaScript版:

JSX
import { useState, useMemo } from 'react'

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: '買い物', completed: false, priority: 'high' },
    { id: 2, text: '洗濯', completed: true, priority: 'low' },
    { id: 3, text: '掃除', completed: false, priority: 'medium' }
  ])
  const [filter, setFilter] = useState('all')
  const [sortBy, setSortBy] = useState('id')
  
  // フィルタリングとソートをメモ化
  const filteredAndSortedTodos = useMemo(() => {
    console.log('フィルタリングとソート実行')
    
    // フィルタリング
    let filtered = todos
    if (filter === 'active') {
      filtered = todos.filter(todo => !todo.completed)
    } else if (filter === 'completed') {
      filtered = todos.filter(todo => todo.completed)
    }
    
    // ソート
    const sorted = [...filtered].sort((a, b) => {
      if (sortBy === 'priority') {
        const priorityOrder = { high: 3, medium: 2, low: 1 }
        return priorityOrder[b.priority] - priorityOrder[a.priority]
      }
      return a.id - b.id
    })
    
    return sorted
  }, [todos, filter, sortBy])
  
  return (
    <div>
      <select value={filter} onChange={(e) => setFilter(e.target.value)}>
        <option value="all">すべて</option>
        <option value="active">未完了</option>
        <option value="completed">完了</option>
      </select>
      
      <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
        <option value="id">ID順</option>
        <option value="priority">優先度順</option>
      </select>
      
      <ul>
        {filteredAndSortedTodos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  )
}

TypeScript版:

TSX
import { useState, useMemo } from 'react'

type Priority = 'high' | 'medium' | 'low'
type FilterType = 'all' | 'active' | 'completed'
type SortType = 'id' | 'priority'

interface Todo {
  id: number
  text: string
  completed: boolean
  priority: Priority
}

function TodoList(): JSX.Element {
  const [todos, setTodos] = useState<Todo[]>([
    { id: 1, text: '買い物', completed: false, priority: 'high' },
    { id: 2, text: '洗濯', completed: true, priority: 'low' },
    { id: 3, text: '掃除', completed: false, priority: 'medium' }
  ])
  const [filter, setFilter] = useState<FilterType>('all')
  const [sortBy, setSortBy] = useState<SortType>('id')
  
  const filteredAndSortedTodos = useMemo(() => {
    console.log('フィルタリングとソート実行')
    
    let filtered = todos
    if (filter === 'active') {
      filtered = todos.filter(todo => !todo.completed)
    } else if (filter === 'completed') {
      filtered = todos.filter(todo => todo.completed)
    }
    
    const sorted = [...filtered].sort((a, b) => {
      if (sortBy === 'priority') {
        const priorityOrder: Record<Priority, number> = { high: 3, medium: 2, low: 1 }
        return priorityOrder[b.priority] - priorityOrder[a.priority]
      }
      return a.id - b.id
    })
    
    return sorted
  }, [todos, filter, sortBy])
  
  return (
    <div>
      <select value={filter} onChange={(e) => setFilter(e.target.value as FilterType)}>
        <option value="all">すべて</option>
        <option value="active">未完了</option>
        <option value="completed">完了</option>
      </select>
      
      <select value={sortBy} onChange={(e) => setSortBy(e.target.value as SortType)}>
        <option value="id">ID順</option>
        <option value="priority">優先度順</option>
      </select>
      
      <ul>
        {filteredAndSortedTodos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  )
}

useCallback

関数自体をメモ化して、React.memoとの組み合わせを正しく機能させる方法を習得できます。

React.memoを使っているのになぜか再レンダリングされる」という典型的なバグの原因と解決策を理解できます。

関数をメモ化

コンポーネントが再レンダリングされるたびに、その内部で定義した関数は毎回新しいオブジェクトとして生成されます。

React.memoでラップした子コンポーネントのpropsに関数を渡すと、「関数が変わった=propsが変わった」と判定されてしまい、メモ化が効きません。

useCallbackを使うことで関数の同一性を保ち、不要な再レンダリングを防ぎます。

JavaScript版:

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

// memoで最適化されたコンポーネント
const Button = memo(function Button({ onClick, children }) {
  console.log('Buttonが再レンダリング')
  return <button onClick={onClick}>{children}</button>
})

function App() {
  const [count, setCount] = useState(0)
  const [text, setText] = useState('')
  
  // ❌ 悪い例:毎回新しい関数が作成される
  // const handleClick = () => {
  //   setCount(count + 1)
  // }
  
  // ✅ 良い例:useCallbackで関数をメモ化
  const handleClick = useCallback(() => {
    setCount(prev => prev + 1)
  }, [])  // 依存配列が空なので一度だけ作成
  
  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <p>カウント: {count}</p>
      {/* textが変わってもButtonは再レンダリングされない */}
      <Button onClick={handleClick}>+1</Button>
    </div>
  )
}

useCallBackを使わない場合、
Appが再レンダリング

handleClick を再定義(新しいオブジェクトとして)

子コンポーネントに渡す

memo が「前回と同じprops?」と比較

handleClick の参照が違う → 「変わった!」と誤検知

子コンポーネントも再レンダリング(本当は不要なのに)

TypeScript版:

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

interface ButtonProps {
  onClick: () => void
  children: React.ReactNode
}

const Button = memo(function Button({ onClick, children }: ButtonProps): JSX.Element {
  console.log('Buttonが再レンダリング')
  return <button onClick={onClick}>{children}</button>
})

function App(): JSX.Element {
  const [count, setCount] = useState<number>(0)
  const [text, setText] = useState<string>('')
  
  const handleClick = useCallback(() => {
    setCount(prev => prev + 1)
  }, [])
  
  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <p>カウント: {count}</p>
      <Button onClick={handleClick}>+1</Button>
    </div>
  )
}

useCallbackと依存配列

依存配列に指定した値が変わったときだけ、関数を再生成します。

たとえばtodo.idが変わったときだけhandleDeleteを作り直すといったコントロールが可能です。

依存配列を正しく設定することで、不必要な再生成を防ぎつつ、古い値を参照するバグも回避できます。

JavaScript版:

JSX
import { useState, useCallback } from 'react'

function TodoItem({ todo, onDelete }) {
  const [isHovered, setIsHovered] = useState(false)
  
  // todoが変わったときだけ関数を再作成
  const handleDelete = useCallback(() => {
    onDelete(todo.id)
  }, [todo.id, onDelete])
  
  return (
    <div
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      <span>{todo.text}</span>
      {isHovered && <button onClick={handleDelete}>削除</button>}
    </div>
  )
}

React.memo + useCallback + useMemo の組み合わせ

3つの最適化ツールを実際のアプリに組み合わせて適用する方法を習得できます。

「どこに何を使えばいいか」の判断基準が身につき、実務レベルのパフォーマンス設計ができるようになります。

実際のアプリでは、これら3つを組み合わせて使います。

TodoAppを例に、子コンポーネントのメモ化(React.memo)・統計計算とフィルタリングのキャッシュ(useMemo)・イベントハンドラの安定化(useCallback) を同時に適用する方法を見ていきましょう。

JavaScript版:

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

// 子コンポーネント
const TodoItem = memo(function TodoItem({ todo, onToggle, onDelete }) {
  console.log('TodoItemが再レンダリング:', todo.id)
  
  return (
    <div>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
      <button onClick={() => onDelete(todo.id)}>削除</button>
    </div>
  )
})

// 親コンポーネント
function TodoApp() {
  const [todos, setTodos] = useState([
    { id: 1, text: '買い物', completed: false },
    { id: 2, text: '洗濯', completed: false }
  ])
  const [filter, setFilter] = useState('all')
  
  // 重い計算をメモ化
  const stats = useMemo(() => {
    console.log('統計を計算')
    return {
      total: todos.length,
      completed: todos.filter(t => t.completed).length,
      active: todos.filter(t => !t.completed).length
    }
  }, [todos])
  
  // フィルタリングをメモ化
  const filteredTodos = useMemo(() => {
    console.log('フィルタリング実行')
    if (filter === 'active') return todos.filter(t => !t.completed)
    if (filter === 'completed') return todos.filter(t => t.completed)
    return todos
  }, [todos, filter])
  
  // 関数をメモ化
  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ))
  }, [])
  
  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id))
  }, [])
  
  return (
    <div>
      <div>
        <button onClick={() => setFilter('all')}>すべて ({stats.total})</button>
        <button onClick={() => setFilter('active')}>未完了 ({stats.active})</button>
        <button onClick={() => setFilter('completed')}>完了 ({stats.completed})</button>
      </div>
      
      <div>
        {filteredTodos.map(todo => (
          <TodoItem
            key={todo.id}
            todo={todo}
            onToggle={handleToggle}
            onDelete={handleDelete}
          />
        ))}
      </div>
    </div>
  )
}

TypeScript版:

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

interface Todo {
  id: number
  text: string
  completed: boolean
}

interface TodoItemProps {
  todo: Todo
  onToggle: (id: number) => void
  onDelete: (id: number) => void
}

const TodoItem = memo(function TodoItem({ 
  todo, 
  onToggle, 
  onDelete 
}: TodoItemProps): JSX.Element {
  console.log('TodoItemが再レンダリング:', todo.id)
  
  return (
    <div>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
      <button onClick={() => onDelete(todo.id)}>削除</button>
    </div>
  )
})

function TodoApp(): JSX.Element {
  const [todos, setTodos] = useState<Todo[]>([
    { id: 1, text: '買い物', completed: false },
    { id: 2, text: '洗濯', completed: false }
  ])
  const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all')
  
  const stats = useMemo(() => {
    console.log('統計を計算')
    return {
      total: todos.length,
      completed: todos.filter(t => t.completed).length,
      active: todos.filter(t => !t.completed).length
    }
  }, [todos])
  
  const filteredTodos = useMemo(() => {
    console.log('フィルタリング実行')
    if (filter === 'active') return todos.filter(t => !t.completed)
    if (filter === 'completed') return todos.filter(t => t.completed)
    return todos
  }, [todos, filter])
  
  const handleToggle = useCallback((id: number): void => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ))
  }, [])
  
  const handleDelete = useCallback((id: number): void => {
    setTodos(prev => prev.filter(todo => todo.id !== id))
  }, [])
  
  return (
    <div>
      <div>
        <button onClick={() => setFilter('all')}>すべて ({stats.total})</button>
        <button onClick={() => setFilter('active')}>未完了 ({stats.active})</button>
        <button onClick={() => setFilter('completed')}>完了 ({stats.completed})</button>
      </div>
      
      <div>
        {filteredTodos.map(todo => (
          <TodoItem
            key={todo.id}
            todo={todo}
            onToggle={handleToggle}
            onDelete={handleDelete}
          />
        ))}
      </div>
    </div>
  )
}

よくある間違い

最適化の「やりすぎ」や「設定ミス」によって起きる典型的なバグを事前に回避できます。

パフォーマンス改善のつもりがかえってコードを壊してしまう落とし穴を知っておくことで、安全に最適化を適用できるようになります。

間違い1:すべてをメモ化する

useMemouseCallbackはメモリを使い、比較処理のコストも発生します。

文字列の結合など軽い処理に使っても意味がなく、むしろコードが読みにくくなるだけです。

最適化は「重い処理」や「頻繁に再レンダリングされるコンポーネント」に絞って適用しましょう。

JSX
// ❌ 悪い例:過剰な最適化
function SimpleComponent({ name }) {
  // シンプルな文字列結合にuseMemoは不要
  const greeting = useMemo(() => `こんにちは、${name}さん`, [name])
  
  return <div>{greeting}</div>
}

// ✅ 良い例:シンプルに書く
function SimpleComponent({ name }) {
  const greeting = `こんにちは、${name}さん`
  return <div>{greeting}</div>
}

間違い2:依存配列を忘れる

useMemouseCallbackの依存配列に使用している変数をすべて含めないと、古い値を参照し続けるバグが発生します。

ESLintのexhaustive-depsルールを有効にしておくと、こうした抜け漏れを自動で検出できます。

JSX
// ❌ 悪い例:依存配列が不完全
const filtered = useMemo(() => {
  return items.filter(item => item.category === selectedCategory)
}, [items])  // selectedCategoryが含まれていない

// ✅ 良い例:すべての依存を含める
const filtered = useMemo(() => {
  return items.filter(item => item.category === selectedCategory)
}, [items, selectedCategory])

間違い3:オブジェクトや配列を依存配列に

オブジェクトや配列はレンダリングのたびに新しい参照が作られるため、React.memoの比較で「変わった」と判定されます。

子コンポーネントにオブジェクト・配列をpropsとして渡す場合は、useMemoでメモ化して参照の同一性を保ちましょう。

JSX
// ❌ 悪い例:毎回新しいオブジェクトが作成される
function Parent() {
  const config = { theme: 'dark' }  // 毎回新しいオブジェクト
  return <Child config={config} />
}

const Child = memo(function Child({ config }) {
  // configは毎回変わるため、memoが効かない
  return <div>{config.theme}</div>
})

// ✅ 良い例:useMemoでメモ化
function Parent() {
  const config = useMemo(() => ({ theme: 'dark' }), [])
  return <Child config={config} />
}

パフォーマンス測定

最適化の効果を数値で確認する方法を習得できます。

「なんとなく速くなった気がする」ではなく、計測に基づいて最適化を判断する習慣が身につきます。

React DevTools Profiler

React DevToolsに内蔵されたProfilerを使うと、各コンポーネントのレンダリング時間をビジュアルで確認できます。

コード上では<Profiler>コンポーネントを使うことで、レンダリングの実時間をログに出力することも可能です。

JSX
import { Profiler } from 'react'

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log(`${id}${phase} フェーズ`)
  console.log(`実際の時間: ${actualDuration}ms`)
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <TodoApp />
    </Profiler>
  )
}

console.timeでの測定

より手軽に特定の処理時間を計測したい場合は、console.time / console.timeEndを使います。

最適化の前後で計測し、改善効果を数値で比較するのに便利です。

JSX
function ExpensiveComponent() {
  console.time('重い処理')
  
  // 重い処理
  let result = 0
  for (let i = 0; i < 1000000; i++) {
    result += i
  }
  
  console.timeEnd('重い処理')
  
  return <div>{result}</div>
}

最適化のガイドライン

いつ最適化すべきで、いつすべきでないかの判断基準が身につきます。

「とりあえず全部メモ化する」「問題が出てから慌てて対処する」という両極端を避け、適切なタイミングで適切な手を打てるようになります。

いつ最適化すべきか?

  1. パフォーマンス問題が実際に発生している
  2. コンポーネントが頻繁に再レンダリングされる
  3. 重い計算やデータ変換がある
  4. 大きなリストを表示している

いつ最適化すべきでないか?

  1. シンプルなコンポーネント
  2. 再レンダリングが速い
  3. 計算が軽い
  4. まだ問題が起きていない

最適化の順序

感覚で最適化するのではなく、以下の順序を守ることで無駄な作業を防げます。

  1. まず計測する(React DevTools Profiler)
  2. ボトルネックを特定
  3. 必要な箇所だけ最適化
  4. 再度計測して効果を確認

まとめ

この記事では、Reactのパフォーマンス最適化を詳しく学びました。

重要なポイント:

  • React.memo:propsが変わらなければ再レンダリングをスキップ
  • useMemo:重い計算結果をメモ化
  • useCallback:関数をメモ化
  • 過剰な最適化は避ける
  • 依存配列を正しく設定する

最適化の3つのツール:

ツール対象
React.memoコンポーネント全体をメモ化
useMemo値や計算結果をメモ化
useCallback関数をメモ化

ベストプラクティス:

  • 実際に問題が起きてから最適化する
  • パフォーマンスを計測してから最適化する
  • シンプルなコンポーネントは最適化しない
  • 依存配列を正確に指定する
  • オブジェクトや配列のpropsはメモ化する

最適化は諸刃の剣です。

コードを複雑にするため、本当に必要な場合だけ適用しましょう!

次のステップ: 次回は、TypeScriptの高度な型定義について学びます。ジェネリクス、ユーティリティ型、型ガードなど、より型安全なReactアプリケーションを作る方法を詳しく解説します!