TypeScriptのGenerics(ジェネリクス)は、型を柔軟に扱うための強力な機能です。特にReactでコンポーネントを作成する際、再利用性の高いコードを書くために重要な役割を果たします。この記事では、Genericsの基本的な概念から、実践的なコンポーネント作成までを解説します。
Genericsとは
Genericsは型の一般化を可能にする機能で、コードの再利用性を高めることができます。型パラメータを使用することで、複数の型で動作する関数やクラスを作成できます。
function identity<T>(arg: T): T {
return arg;
}
// 使用例
const numberResult = identity<number>(42); // number型
const stringResult = identity<string>("Hello"); // string型ReactコンポーネントでのGenericsの活用
Reactでは、プロップスの型を柔軟に定義するためにGenericsを使用できます。以下は基本的な例です:
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}
// 使用例
const App = () => {
const numbers = [1, 2, 3, 4, 5];
return (
<List
items={numbers}
renderItem={(number) => <span>{number * 2}</span>}
/>
);
};制約付きGenerics
特定の型や型の特徴を持つものに限定したい場合は、extendsキーワードを使用します:
interface HasId {
id: number;
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
// 使用例
interface User extends HasId {
name: string;
email: string;
}
const users: User[] = [
{ id: 1, name: "John", email: "john@example.com" },
{ id: 2, name: "Jane", email: "jane@example.com" }
];
const user = findById(users, 1);実践的なコンポーネント例
データ取得と表示を行う汎用的なコンポーネントの例:
interface FetchContainerProps<T> {
url: string;
render: (data: T) => React.ReactNode;
}
function FetchContainer<T>({ url, render }: FetchContainerProps<T>) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
});
}, [url]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return null;
return <>{render(data)}</>;
}
// 使用例
interface Post {
id: number;
title: string;
body: string;
}
const PostComponent = () => (
<FetchContainer<Post>
url="https://api.example.com/posts/1"
render={(post) => (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
)}
/>
);ベストプラクティス
1. 型パラメータには意味のある名前をつける(T, U, V ではなく、TData, TProps など)
2. 必要な場合のみGenericsを使用し、過度な抽象化を避ける
3. 適切な制約を設定し、型の安全性を確保する
4. デフォルトの型パラメータを活用する
interface DataContainerProps<TData = any> {
data: TData;
loading?: boolean;
}
const DataContainer = <TData = any,>({ data, loading }: DataContainerProps<TData>) => {
if (loading) return <div>Loading...</div>;
return <div>{JSON.stringify(data)}</div>;
};Genericsを使いこなすことで、型安全で再利用可能なコンポーネントを作成できます。適切に活用することで、コードの保守性と再利用性が大幅に向上します。