Reactのカスタムフックは、状態管理やロジックの再利用をシンプルにする強力なツールです。TypeScriptを活用することで、カスタムフックに型安全性を加え、予期しないエラーを防ぎながら、再利用性の高いコードを構築できます。この章では、型安全なカスタムフックの作成、再利用可能なロジックの設計、実用例について解説します。
型安全なカスタムフックの作成
カスタムフックは、use
で始まる関数として定義されます。TypeScriptでは、引数と戻り値に型を定義することで、フックの意図を明確にし、再利用時の安全性を高めます。
基本例
以下は、カウントを管理するカスタムフックの例です。
tsx
import { useState } from 'react';
const useCounter = (initialValue: number = 0): [number, () => void, () => void] => {
const [count, setCount] = useState<number>(initialValue);
const increment = () => setCount((prev) => prev + 1);
const decrement = () => setCount((prev) => prev - 1);
return [count, increment, decrement];
};
export default useCounter;
解説
- 引数の型:
initialValue
のデフォルト値を指定しつつ型を明示。 - 戻り値の型: 配列型で各要素の型を明確に指定。
- 安全性: 関数が期待通りのデータを扱うことを保証。
再利用可能なロジックの設計
カスタムフックは、ロジックを再利用可能にするために設計されます。TypeScriptを使用して汎用性を高める方法を見てみましょう。
フック内でのジェネリクスの使用
ジェネリクスを使用することで、フックの柔軟性を向上させることができます。
例: フォーム管理フック
tsx
import { useState } from 'react';
const useForm = <T extends Record<string, any>>(initialValues: T) => {
const [values, setValues] = useState<T>(initialValues);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
setValues({
...values,
[name]: value,
});
};
const resetForm = () => setValues(initialValues);
return { values, handleChange, resetForm };
};
export default useForm;
使用例
tsx
interface FormValues {
username: string;
email: string;
}
const App = () => {
const { values, handleChange, resetForm } = useForm<FormValues>({
username: '',
email: '',
});
return (
<div>
<input
type="text"
name="username"
value={values.username}
onChange={handleChange}
/>
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
/>
<button onClick={resetForm}>Reset</button>
</div>
);
};
export default App;
解説
- ジェネリクス
<T>
: フォームのフィールドが柔軟に変更可能。 - 型安全なフィールド操作:
T
のフィールドのみを操作可能。
実用例: データ取得フック
データ取得のロジックをカスタムフックとして切り出すことで、再利用性を高めることができます。
データ取得フックの例
tsx
import { useEffect, useState } from 'react';
import axios from 'axios';
const useFetch = <T>(url: string) => {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await axios.get<T>(url);
setData(response.data);
} catch (err: any) {
setError(err.message || 'An error occurred');
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
export default useFetch;
使用例
tsx
interface User {
id: number;
name: string;
}
const App = () => {
const { data, loading, error } = useFetch<User[]>('https://jsonplaceholder.typicode.com/users');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
export default App;
解説
- ジェネリクス
<T>
: 任意のデータ型に対応可能。 - エラーハンドリング: ロード中とエラー時の状態を明確に管理。
カスタムフックを使用することで、型安全性を保ちながらロジックを再利用可能にできます。フォーム管理やデータ取得といったよくあるユースケースに対応する汎用的なフックを設計することで、コードの効率と可読性を向上させましょう。