記事一覧に戻る
💻
Tech
2025/8/5
6分

React Hooks最新ベストプラクティス 2025

ReactHooksTypeScriptパフォーマンス

React 18.2以降の最新環境において、パフォーマンスと保守性を両立するHooksの使い方をまとめました。 実際のプロジェクトで使える実践的なテクニックを中心に解説します。

1. useState の最適化パターン

useStateは最も基本的なフックですが、適切な使い方で大幅なパフォーマンス改善が可能です。

❌ 避けるべきパターン

📄 BadExample.tsx
// オブジェクトの直接更新(再レンダリングの原因)
const [user, setUser] = useState({ name: '', email: '' })

const updateName = (newName: string) => {
  user.name = newName // ❌ 直接変更
  setUser(user) // ❌ 同じ参照
}
typescript

✅ 推奨パターン

📄 GoodExample.tsx
// イミュータブルな更新パターン
const [user, setUser] = useState({ name: '', email: '' })

const updateName = useCallback((newName: string) => {
  setUser(prev => ({ ...prev, name: newName })) // ✅ 新しいオブジェクト
}, [])

// 複雑な状態はuseReducerを検討
interface UserState {
  name: string
  email: string
  isLoading: boolean
}

const userReducer = (state: UserState, action: any) => {
  switch (action.type) {
    case 'SET_NAME':
      return { ...state, name: action.payload }
    case 'SET_EMAIL':
      return { ...state, email: action.payload }
    case 'SET_LOADING':
      return { ...state, isLoading: action.payload }
    default:
      return state
  }
}
typescript

2. useEffect の依存配列最適化

useEffectの依存配列は、無限レンダリングやメモリリークの原因になりやすい部分です。

📄 useEffectOptimization.tsx
// ✅ 依存配列の最適化
const UserProfile = ({ userId }: { userId: string }) => {
  const [user, setUser] = useState<User | null>(null)
  const [loading, setLoading] = useState(false)

  // APIコールをuseCallbackでメモ化
  const fetchUser = useCallback(async (id: string) => {
    setLoading(true)
    try {
      const response = await fetch(`/api/users/${id}`)
      const userData = await response.json()
      setUser(userData)
    } catch (error) {
      console.error('ユーザー取得エラー:', error)
    } finally {
      setLoading(false)
    }
  }, []) // 依存配列は空

  useEffect(() => {
    fetchUser(userId)
  }, [fetchUser, userId]) // fetchUserとuserIdに依存

  // クリーンアップも忘れずに
  useEffect(() => {
    return () => {
      // クリーンアップ処理
      setUser(null)
      setLoading(false)
    }
  }, [])

  if (loading) return <div>読み込み中...</div>
  if (!user) return <div>ユーザーが見つかりません</div>

  return <div>{user.name}</div>
}
typescript

3. カスタムフックの設計パターン

再利用可能なロジックはカスタムフックに切り出すことで、コンポーネントをクリーンに保てます。

📄 hooks/useAPI.ts
// 汎用APIフック
interface UseAPIResult<T> {
  data: T | null
  loading: boolean
  error: string | null
  refetch: () => Promise<void>
}

export function useAPI<T>(url: string, options?: RequestInit): UseAPIResult<T> {
  const [data, setData] = useState<T | null>(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const fetchData = useCallback(async () => {
    setLoading(true)
    setError(null)
    
    try {
      const response = await fetch(url, options)
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      const result = await response.json()
      setData(result)
    } catch (err) {
      setError(err instanceof Error ? err.message : '不明なエラー')
    } finally {
      setLoading(false)
    }
  }, [url, JSON.stringify(options)])

  useEffect(() => {
    fetchData()
  }, [fetchData])

  return { data, loading, error, refetch: fetchData }
}

// 使用例
const UserList = () => {
  const { data: users, loading, error, refetch } = useAPI<User[]>('/api/users')

  if (loading) return <div>読み込み中...</div>
  if (error) return <div>エラー: {error}</div>

  return (
    <div>
      <button onClick={refetch}>再読み込み</button>
      {users?.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  )
}
typescript

4. パフォーマンス最適化のポイント

🚀 最適化チェックリスト

  • React.memo で不要な再レンダリングを防ぐ
  • useMemo で重い計算をメモ化
  • useCallback で関数をメモ化
  • • 状態の分割で更新範囲を限定
  • • 適切な key 属性でリスト最適化
📄 OptimizedComponent.tsx
// メモ化を活用した最適化例
interface UserItemProps {
  user: User
  onUpdate: (id: string, data: Partial<User>) => void
}

const UserItem = React.memo(({ user, onUpdate }: UserItemProps) => {
  // 個別のアクションをメモ化
  const handleNameChange = useCallback((newName: string) => {
    onUpdate(user.id, { name: newName })
  }, [user.id, onUpdate])

  // 重い計算をメモ化
  const displayName = useMemo(() => {
    return user.name.toUpperCase().replace(/[^A-Z]/g, '')
  }, [user.name])

  return (
    <div>
      <span>{displayName}</span>
      <input 
        value={user.name}
        onChange={e => handleNameChange(e.target.value)}
      />
    </div>
  )
})

// 親コンポーネントでも最適化
const UserList = () => {
  const [users, setUsers] = useState<User[]>([])

  // 更新関数をメモ化(UserItemの再レンダリングを防ぐ)
  const handleUserUpdate = useCallback((id: string, data: Partial<User>) => {
    setUsers(prev => prev.map(user => 
      user.id === id ? { ...user, ...data } : user
    ))
  }, [])

  return (
    <div>
      {users.map(user => (
        <UserItem 
          key={user.id}
          user={user}
          onUpdate={handleUserUpdate}
        />
      ))}
    </div>
  )
}
typescript

まとめ

React Hooksを効果的に使うことで、関数コンポーネントでもクラスコンポーネント以上のパフォーマンスと保守性を実現できます。 特に以下の点を意識することが重要です:

  • 状態の更新は常にイミュータブルに
  • useEffectの依存配列を適切に管理
  • カスタムフックで関心の分離
  • 必要な場所でのみメモ化を使用
  • パフォーマンス測定ツールでの検証

実際のプロジェクトでのReact Hooks活用例は、Lovateプロジェクトでも確認できます。リアルタイムチャットやAI統合での実装パターンを参考にしてください。