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統合での実装パターンを参考にしてください。