TypeScriptで配列・オブジェクト操作関数をジェネリクスで安全に書く方法
生徒
「先生、TypeScriptで配列やオブジェクトを扱う関数を作りたいんですが、型が合わなくてエラーになることが多いんです…」
先生
「いい質問ですね。TypeScriptではジェネリクス(Generics)を使うと、型を安全に扱える便利な方法がありますよ。」
生徒
「ジェネリクスって聞いたことありますが、難しそうです…配列やオブジェクトに使えるんですか?」
先生
「もちろんです!配列にもオブジェクトにも使えます。初心者でも分かるように、実例で一緒に学んでいきましょう。」
1. ジェネリクス(Generics)とは?
まず、ジェネリクスとは「どんな型にも対応できる仕組み」のことです。たとえば、数値でも文字列でも同じ関数で処理したいときに便利です。TypeScriptでは、関数名の後に<T>という記号を使って「型の変数」を定義します。このTは「Type(型)」の頭文字で、どんな型でも入る「型の箱」と考えるとわかりやすいです。
たとえば、配列の最初の要素を取得する関数を作ってみましょう。
function getFirst<T>(arr: T[]): T {
return arr[0];
}
// 文字列の配列
const names = ["太郎", "花子", "次郎"];
console.log(getFirst(names)); // 太郎
// 数値の配列
const numbers = [10, 20, 30];
console.log(getFirst(numbers)); // 10
ここでは、Tが自動的に「文字列」や「数値」として推論されます。TypeScriptの型推論によって、型を明示的に指定しなくても安全に扱えるのが特徴です。
2. 配列操作をジェネリクスで安全に書く
配列操作では、「要素を追加する」「要素を検索する」「要素を削除する」といった処理がよくあります。これらもジェネリクスを使うと型安全に書けます。
配列に要素を追加する関数
まずは、配列に新しい要素を追加する関数をジェネリクスで書いてみましょう。
function addItem<T>(array: T[], item: T): T[] {
return [...array, item];
}
const fruits = ["りんご", "みかん"];
const newFruits = addItem(fruits, "ぶどう");
console.log(newFruits);
["りんご", "みかん", "ぶどう"]
この関数では、配列の型(T[])と追加する要素(item: T)の型をそろえることで、型エラーを防げます。もし間違って数値を入れようとすると、TypeScriptが自動で警告を出してくれるので安心です。
配列から特定の要素を探す関数
function findItem<T>(array: T[], target: T): T | undefined {
return array.find(item => item === target);
}
const colors = ["赤", "青", "緑"];
console.log(findItem(colors, "青")); // 青
このように、どんな型でも同じロジックで安全に処理できます。TypeScriptが「この配列は文字列配列だな」と自動で判断してくれるため、エラーが出ることはありません。
3. オブジェクト操作をジェネリクスで安全に書く
次に、オブジェクトを扱うときにジェネリクスを使う方法を見ていきましょう。オブジェクトとは、キーと値(key-value)のセットで構成されたデータです。
オブジェクトから特定のキーの値を取得する
ジェネリクスに加えて、keyofというキーワードを使うと、オブジェクトのキー名だけを安全に指定できます。
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "太郎", age: 25 };
console.log(getValue(user, "name")); // 太郎
K extends keyof Tは「オブジェクトTのキーだけを受け取れる」という意味です。つまり、getValue(user, "gender")のように存在しないキーを指定すると、コンパイルエラーになります。
オブジェクトのプロパティを更新する
次に、オブジェクトの特定のプロパティを変更する関数を作ってみます。
function updateValue<T, K extends keyof T>(obj: T, key: K, value: T[K]): T {
return { ...obj, [key]: value };
}
const product = { name: "ノートパソコン", price: 120000 };
const newProduct = updateValue(product, "price", 98000);
console.log(newProduct);
{ name: "ノートパソコン", price: 98000 }
このように書くと、priceが数値であることをTypeScriptが自動で判定し、間違った型(たとえば文字列)を入れようとするとエラーになります。
4. ジェネリクスを使うメリット
- 型安全:間違ったデータ型を扱うミスを防げる
- 再利用性が高い:どんなデータ型にも対応できる関数を一度で書ける
- 保守性が高い:エラーが起きにくく、将来的な修正も楽になる
配列やオブジェクトを扱うとき、ジェネリクスを使うことで「型がズレてエラーになる」という悩みを解消できます。コードの品質が上がるだけでなく、開発スピードも向上します。
まとめ
配列・オブジェクト操作にジェネリクスを使う意味を振り返る
この記事では、TypeScriptで配列やオブジェクトを操作する関数を、ジェネリクスを使って安全に書く方法について詳しく解説してきました。配列やオブジェクトは、アプリケーション開発の中で非常に頻繁に使われるデータ構造ですが、その分「型が合わない」「意図しないデータが入ってしまう」といったエラーが起きやすいポイントでもあります。 そこで活躍するのが、TypeScriptのジェネリクスです。ジェネリクスを使うことで、「どんな型でも使えるが、型のルールは必ず守る」という、安全性と柔軟性を両立したコードを書くことができます。これは、JavaScriptにはないTypeScriptならではの大きな強みです。
特に配列操作では、「配列の要素の型」と「追加・検索する値の型」を一致させることが重要でした。ジェネリクスを使えば、関数を呼び出した時点でTypeScriptが型を自動的に推論し、間違った型を渡した場合にはコンパイル時にエラーとして教えてくれます。これにより、実行してから気付くバグを大幅に減らすことができます。
オブジェクト操作とkeyof・ジェネリクスの組み合わせ
オブジェクト操作においては、ジェネリクスとあわせてkeyofを使うことで、さらに安全なコードが書けることを学びました。オブジェクトのキーは文字列として扱われがちですが、TypeScriptでは「そのオブジェクトに実際に存在するキーだけ」を型として扱うことができます。
これにより、存在しないプロパティを指定してしまうミスや、プロパティの型と異なる値を代入してしまうミスを未然に防ぐことができます。
update関数の例では、プロパティ名と値の型が正しく対応しているかどうかをTypeScriptが自動的にチェックしてくれました。こうした仕組みは、アプリケーションの規模が大きくなるほど効果を発揮し、保守性の高いコードにつながります。
配列・オブジェクトをまとめて扱うジェネリクスサンプル
function replaceItem<T>(array: T[], index: number, newItem: T): T[] {
return array.map((item, i) => (i === index ? newItem : item));
}
type Profile = {
name: string;
age: number;
};
const profiles: Profile[] = [
{ name: "太郎", age: 25 },
{ name: "花子", age: 30 }
];
const updatedProfiles = replaceItem(profiles, 1, { name: "花子", age: 31 });
console.log(updatedProfiles);
このサンプルでは、配列操作とオブジェクト型を組み合わせたジェネリクス関数を使っています。配列の中身がどんな型であっても、同じ関数を安全に使える点がポイントです。 Profile型の配列であることをTypeScriptが理解しているため、newItemに不正な構造のオブジェクトを渡すとエラーになります。このように、ジェネリクスは「自由に書けるけれど、間違いは許さない」仕組みとして働きます。
生徒
「ジェネリクスって難しいイメージがありましたけど、配列やオブジェクトの型を守るための仕組みだと分かってきました。」
先生
「そうですね。ジェネリクスは特別な魔法というより、型を正しく伝えるための道具なんです。」
生徒
「keyofと組み合わせると、存在しないプロパティを使えなくなるのが安心でした。」
先生
「それがTypeScriptの強みですね。人が気を付けなくても、型がミスを防いでくれます。」
生徒
「これからは配列やオブジェクトを扱う関数を書くとき、まずジェネリクスが使えないか考えてみます。」
先生
「その意識があれば十分です。ジェネリクスを使いこなせるようになると、TypeScriptの理解が一段深まりますよ。」