こんにちは!フロントエンドの現場を渡り歩いている、チーフアーキテクトの私です。
Reactを触り始めて、「よし、コンポーネントを再利用して綺麗にコードを書くぞ!」と意気込んだものの、TypeScriptの型定義の壁にぶつかって頭を抱えていませんか?特に、リスト表示用の汎用的なコンポーネントを作ろうとしたとき、「どんなデータが来るかわからないのに、どうやって型を付ければいいんだ…!」と絶望した経験、きっと誰もあります。私も昔は夜な夜なエラー画面と睨めっこしていました。
でも、大丈夫ですよ。安心してください。
今日は、そんなモヤモヤを綺麗に解消してくれる魔法の道具「ジェネリクス(
—
1. ジェネリクスってなんだろう?(お弁当箱の例え)
いきなり `
ジェネリクスを身近なもので例えるなら、「何を入れてもいい、でも中身をちゃんとラベルで教えてくれる魔法のオーダーメイドお弁当箱」です。
例えば、あなたが「何でも綺麗に並べられるお皿」を作るとしましょう。
お皿自体は1種類なのに、今日乗せるのは「美味しそうな唐揚げ」かもしれないし、明日乗せるのは「みずみずしいイチゴ」かもしれません。
もし、最初から「このお皿には唐揚げしか乗せません!」と頑なに決めてしまうと、イチゴを乗せたいときに新しいお皿をわざわざ作らなきゃいけなくなりますよね。それは面倒くさいし、コードの無駄遣いです。
「どんな食材(データ)が来るかは、お皿を使うその瞬間まで決まっていなくていい。ただ、お皿の上に何を乗せたかは、TypeScriptにちゃんと覚えておいてほしい!」
これを叶えてくれるのが、ジェネリクスなのです。
—
2. なぜ、リストコンポーネントでジェネリクスが必要なの?
Web制作の現場でよくあるのが、「ユーザーの一覧リスト」や「商品のカート一覧リスト」など、色々なデータを綺麗に並べて表示するコンポーネントです。
もしジェネリクスを使わないで、無理やり型をつけようとするとどうなるでしょうか?
// 🚫 昔の私がやりがちだったダメな例(型がガチガチに固定されちゃう)
type User = {
id: number;
name: string;
};
type UserListProps = {
items: User[];
renderItem: (item: User) => React.ReactNode;
};
// これだと、User型のデータしか扱えない…!
// 商品データ(Product)を表示したくなったら、わざわざ ProductList という別のコンポーネントをコピペで作らなきゃいけないの?
「うわ、めんどくさそう…」って思いますよね。そうなんです。データが変わるたびに似たようなコンポーネントを量産するのは、プログラマーのサボり癖(いい意味での効率化!)に反します。
ここでジェネリクス `
—
3. 実践!ジェネリクスを使ったリストコンポーネントを作ってみよう
百聞は一見にしかず。実際にコードを見てみましょう。
今回は、ユーザーリストでも商品リストでも、なんでも受け止められる「万能リストコンポーネント」を作ります。
エディタを開いて、以下のコードを眺めてみてください。日本語でたっぷりコメントを入れているので、心の準備はいいですか?いざ、参ります!
import React from ‘react’;
// ==========================================
// 1. 万能リストコンポーネントのProps型定義
// ==========================================
// コンポーネント名のすぐ後ろにある
// 「T」という文字は、自由に変更できますが、Typeの「T」を使うのが業界の暗黙の了解です。
type GenericListProps
// どんなデータが入っているか分からない配列(Userかもしれないし、Productかもしれない)
items: T[];
// 「1つのアイテムをどうやって画面に描画するか」を外から教えてもらう関数
// 引数に「T型のアイテム」を受け取り、JSX(画面のパーツ)を返す役割を持ちます
renderItem: (item: T) => React.ReactNode;
};
// ==========================================
// 2. コンポーネント本体の定義
// ==========================================
// ※ポイント:アロー関数でジェネリクスを使う場合、TypeScriptに「これはJSXのタグではなく、ジェネリクスだよ!」と教えるために、
// `
export const GenericList =
return (
-
{items.map((item, index) => (
- {renderItem(item)}
// 配列を展開して、中身の描画は外から渡された renderItem 関数におまかせします!
))}
);
};
どうでしょう?この `GenericList` コンポーネント自体は、中に「ユーザー」が入っているのか「商品」が入っているのか、一切知る必要がありません。ただ、「受け取ったものを順番に並べる」という自分の仕事にだけ集中しています。これが美しいコンポーネント設計の第一歩です。
—
4. 実際に使ってみる(お買い物の場面を想像して)
それでは、先ほど作った万能な `GenericList` を、実際の画面で使ってみましょう。
今回は「ユーザー一覧」と「商品一覧」の2パターンを表示してみます。
import React from ‘react’;
import { GenericList } from ‘./GenericList’; // さっき作ったコンポーネント
// パターンA:ユーザーデータの型とモック
type User = {
id: number;
name: string;
role: string;
};
const users: User[] = [
{ id: 1, name: ‘田中 太郎’, role: ‘管理者’ },
{ id: 2, name: ‘佐藤 花子’, role: ‘エディター’ },
];
// パターンB:商品データの型とモック
type Product = {
id: number;
title: string;
price: number;
};
const products: Product[] = [
{ id: 1, title: ‘極上のコーヒー豆’, price: 1200 },
{ id: 2, title: ‘手作りのマグカップ’, price: 2500 },
];
// ==========================================
// 画面全体の親コンポーネント
// ==========================================
export const App = () => {
return (
社員名簿リスト
{/
ここで GenericList に User 型が自動的に推論されます!
item のプロパティ(name や role)にアクセスするとき、ちゃんと補完が効くので感動しますよ。
/}
)}
/>
おすすめ商品リスト
{/
同じ GenericList なのに、今度は Product 型として動いています!
これがジェネリクスの真骨頂です。
/}
)}
/>
);
};
ここで感動してほしいポイントがあります。
`renderItem={(user) => …}` と書いた瞬間、TypeScriptは「あ、今回はUser型の配列を渡しているんだな、じゃあ引数の `user` は自動的に `User` 型として扱おう!」と賢く察知してくれます。
そのため、エディタ上で `user.` と打った瞬間に `name` や `role` がサジェスト(補完)されるのです。型安全を保ちながら、コードの使い回しが劇的にラクになる――これがジェネリクスの最高の魅力です。
—
5. つまずきやすいポイントと、そっと寄り添うアドバイス
初学者の頃、私もここで何度もつまずきました。みなさんがハマりやすい罠を先にシェアしておきますね。
1. アロー関数で `
先ほどのコードでもサラッと触れましたが、TypeScriptとJSXの組み合わせでは、コンポーネントの型定義で `
もし「なんだこの赤い波線は…!」とイライラしたら、カンマを忘れていないか疑ってみてください。
2. 「なんだか難しそう」と避けてしまうこと
最初は複雑に見えるかもしれませんが、無理に最初から自分でジェネリクスをバリバリ書く必要はありません。
「あ、このコンポーネント、扱うデータが変わるだけで中身の構造は一緒だな」と気づいたとき、「そういえば、あの記事でジェネリクスって言ってたな」と思い出して、コピペから改造するくらいの気持ちで触ってみてください。
—
おわりに
ジェネリクス(
ReactとTypeScriptの組み合わせは、最初は厳しく感じることも多いですが、型という「頼れる相棒」ができると、大規模な開発になればなるほど、バグを未然に防いでくれる最高の味方になってくれます。
焦らず、一歩ずつ、ご自身のペースでコードを書いていきましょう。
あなたのフロントエンドライフが、少しでも楽しく快適になりますように!チーフアーキテクトの私から、心より応援しています。

コメント