📝 React Hooksのベストプラクティス
useEffectの依存配列やカスタムフックの実装など、実践的なHooksの使い方を解説します
React Hooksのベストプラクティス
React Hooksが導入されてから、コンポーネントの状態管理や副作用の処理が大きく変わりました。今回は実際の開発で役立つHooksのベストプラクティスを紹介します。
useEffectの依存配列を正しく管理する
useEffectの依存配列は、Reactの核心的な概念の一つです。正しく理解することで、不要な再レンダリングやメモリリークを防げます。
避けるべきパターン
// ❌ 依存配列を空にしすぎる
useEffect(() => {
fetchUserData(userId);
}, []); // userIdが変わっても実行されない
// ❌ 依存配列を省略する
useEffect(() => {
setCount(count + 1);
}); // 無限ループの原因
推奨パターン
// ✅ 必要な依存関係を正しく指定
useEffect(() => {
const fetchData = async () => {
const data = await fetchUserData(userId);
setUserData(data);
};
fetchData();
}, [userId]); // userIdが変わった時のみ実行
// ✅ useCallbackで関数をメモ化
const handleSubmit = useCallback((formData) => {
submitForm(formData);
}, []);
useEffect(() => {
handleSubmit(data);
}, [data, handleSubmit]);
カスタムフックで共通ロジックを抽出
繰り返し使用されるロジックは、カスタムフックとして抽出することで再利用性が向上します。
APIリクエストのカスタムフック
interface UseApiState<T> {
data: T | null;
loading: boolean;
error: string | null;
}
export const useApi = <T>(url: string): UseApiState<T> => {
const [state, setState] = useState<UseApiState<T>>({
data: null,
loading: true,
error: null,
});
useEffect(() => {
const fetchData = async () => {
try {
setState(prev => ({ ...prev, loading: true, error: null }));
const response = await fetch(url);
if (!response.ok) {
throw new Error('API request failed');
}
const data = await response.json();
setState({ data, loading: false, error: null });
} catch (error) {
setState({
data: null,
loading: false,
error: error instanceof Error ? error.message : 'Unknown error',
});
}
};
fetchData();
}, [url]);
return state;
};
使用例
const UserProfile = ({ userId }: { userId: string }) => {
const { data: user, loading, error } = useApi<User>(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
};
useMemoとuseCallbackの適切な使用
パフォーマンス最適化のためのフックですが、過度な使用は逆効果になることもあります。
useMemoの適切な使用
const ExpensiveComponent = ({ items, filter }: Props) => {
// ✅ 計算コストの高い処理をメモ化
const filteredItems = useMemo(() => {
return items.filter(item => {
// 複雑なフィルタリングロジック
return performExpensiveFiltering(item, filter);
});
}, [items, filter]);
// ❌ 単純な計算はメモ化不要
const simpleCount = useMemo(() => items.length, [items]);
return (
<div>
{filteredItems.map(item => (
<Item key={item.id} item={item} />
))}
</div>
);
};
useReducerで複雑な状態管理
複数の状態が相互に関連する場合は、useReducerを使用することで状態管理がシンプルになります。
interface FormState {
values: Record<string, string>;
errors: Record<string, string>;
isSubmitting: boolean;
}
type FormAction =
| { type: 'SET_FIELD'; field: string; value: string }
| { type: 'SET_ERROR'; field: string; error: string }
| { type: 'CLEAR_ERRORS' }
| { type: 'SET_SUBMITTING'; isSubmitting: boolean };
const formReducer = (state: FormState, action: FormAction): FormState => {
switch (action.type) {
case 'SET_FIELD':
return {
...state,
values: { ...state.values, [action.field]: action.value },
errors: { ...state.errors, [action.field]: '' },
};
case 'SET_ERROR':
return {
...state,
errors: { ...state.errors, [action.field]: action.error },
};
case 'CLEAR_ERRORS':
return { ...state, errors: {} };
case 'SET_SUBMITTING':
return { ...state, isSubmitting: action.isSubmitting };
default:
return state;
}
};
export const useForm = (initialValues: Record<string, string>) => {
const [state, dispatch] = useReducer(formReducer, {
values: initialValues,
errors: {},
isSubmitting: false,
});
const setField = useCallback((field: string, value: string) => {
dispatch({ type: 'SET_FIELD', field, value });
}, []);
const setError = useCallback((field: string, error: string) => {
dispatch({ type: 'SET_ERROR', field, error });
}, []);
return { state, setField, setError, dispatch };
};
まとめ
React Hooksを効果的に使用するためのポイント:
- 依存配列を正しく管理し、不要な再実行を防ぐ
- カスタムフックで共通ロジックを再利用可能にする
- useMemo/useCallbackは本当に必要な場面でのみ使用する
- useReducerで複雑な状態管理をシンプルにする
これらのベストプラクティスを意識することで、より保守性が高く、パフォーマンスの良いReactアプリケーションを構築できます。