はじめてのTypeScriptプログラミング入門 30日コース | Generics・Utility Types・Class・Module - Day 22:Generics

TypeScript 30日で身につけるTypeScript
スポンサーリンク
スポンサーリンク
  1. Day 22:Genericsで「型に強い汎用関数」を手に入れる
  2. Genericsとは:型に対する「変数」を持つイメージ
    1. <T> は「型のためのパラメータ」です
  3. Generic Functionの基本形を押さえる
    1. まずはシンプルな例から
    2. 型推論で <T> を省略できることも多いです
  4. Step 1:文字列・数値・Objectを処理できる汎用関数を考える
  5. Step 2:ジェネリック関数として定義する
    1. 型パラメータTを使った汎用関数
  6. Step 3:文字列・数値・Objectで実際に使ってみる
    1. 文字列を渡す
    2. 数値を渡す
    3. Objectを渡す
  7. Step 4:型推論を使って、もっとシンプルに書いてみる
  8. Step 5:もう少し実用的な処理を足してみる
    1. typeof を使って軽く分岐する例
  9. Day 22 練習テンプレート:汎用ログ関数+少し応用
  10. Day 22のまとめ
  11. Day 22:Generics演習で「どんな配列でも扱える汎用関数」を作る
  12. Genericsの基本イメージをもう一度整理する
    1. 「型のための変数」としての <T>
  13. Step 1:getFirst<T>() を作る
    1. どんな配列でも「最初の要素」を返す汎用関数
    2. 実際に使ってみる
  14. Step 2:getLast<T>() を作る
    1. 配列の「最後の要素」を返す汎用関数
    2. 実際に使ってみる
  15. Step 3:reverse<T>() を作る
    1. 配列を「逆順」にした新しい配列を返す汎用関数
    2. 実際に使ってみる
  16. Step 4:findById<T>() を作る
    1. 「idプロパティを持つオブジェクト配列」から、指定IDの要素を探す
    2. 実際に使ってみる
  17. Day 22 練習テンプレート:4つのジェネリック関数をまとめて試す
  18. Day 22 演習のまとめ

Day 22:Genericsで「型に強い汎用関数」を手に入れる

第4週に入り、いよいよTypeScriptの“応用編”に入っていきます。 Day 22のテーマは Generics(ジェネリクス)。 ここでは、「型を引数として扱う」という少し不思議だけれど、とても強力な仕組みを体験していきます。

到達イメージはこうです。

文字列・数値・オブジェクトなど、いろいろな型を 「安全に」「使い回せる」汎用関数で扱えるようになる。

Genericsとは:型に対する「変数」を持つイメージ

<T> は「型のためのパラメータ」です

Genericsをひと言でいうと、

「型を後から差し込めるようにする仕組み」

です。

そのときに使うのが <T> という記法です。

function identity<T>(value: T): T {
  return value;
}
TypeScript

ここでのポイントは、

  • T は「型の名前」
  • value: T は「valueはT型ですよ」
  • : T(戻り値)も「T型を返しますよ」

という意味になっていることです。

T は「型のための変数」のようなもので、 関数を呼び出すときに「このTにはstringを入れよう」「numberを入れよう」と決めて使います。

Generic Functionの基本形を押さえる

まずはシンプルな例から

先ほどの identity 関数を、もう少し丁寧に見てみます。

// ジェネリック関数の基本形
function identity<T>(value: T): T {
  // valueの型はT
  return value; // T型の値をそのまま返す
}

// 呼び出し例
const s = identity<string>("hello"); // T = string
const n = identity<number>(123);     // T = number
TypeScript

重要ポイント:

  • <string> と書いた瞬間、T は string に置き換えられます。
  • <number> と書いた瞬間、T は number に置き換えられます。
  • 関数の中身は同じでも、「どの型で使うか」を後から決められるのがGenericsの強みです。

型推論で <T> を省略できることも多いです

TypeScriptは賢いので、引数から T を推論してくれます。

const s2 = identity("hello"); // Tはstringと推論される
const n2 = identity(123);     // Tはnumberと推論される
TypeScript

Genericsは「型を引数として扱う」仕組みですが、 毎回 <T> を書かないといけないわけではなく、 多くの場合は「TypeScriptがいい感じに推論してくれる」と思っておいて大丈夫です。

Step 1:文字列・数値・Objectを処理できる汎用関数を考える

ここから、実践テーマに入っていきます。

文字列・数値・Objectを処理できる汎用関数を作る。

まずは、「どんな処理をする関数にするか」を決めましょう。

今回は、次のような関数を目指します。

  • 引数にどんな型でも受け取れる(string / number / objectなど)
  • 受け取った値を「ログに出しつつ、そのまま返す」

つまり、「型安全なログ付きidentity関数」のようなイメージです。

Step 2:ジェネリック関数として定義する

型パラメータTを使った汎用関数

// どんな型でも受け取ってログを出し、そのまま返す汎用関数
function logAndReturn<T>(value: T): T {
  console.log("値:", value); // ここではvalueの型はT
  return value;              // T型の値をそのまま返す
}
TypeScript

ここでのポイントは、

  • function logAndReturn<T>(value: T): T
    • <T>:この関数は「型パラメータT」を持っています
    • value: T:引数valueはT型です
    • : T:戻り値もT型です

という構造になっていることです。

Step 3:文字列・数値・Objectで実際に使ってみる

文字列を渡す

const strResult = logAndReturn<string>("Hello Generics");
// T = string
// valueの型はstring
// 戻り値の型もstring
TypeScript

TypeScript的には、

  • value は string
  • strResult も string

として扱われます。

数値を渡す

const numResult = logAndReturn<number>(42);
// T = number
// valueの型はnumber
// 戻り値の型もnumber
TypeScript

ここでも、

  • value は number
  • numResult も number

として扱われます。

Objectを渡す

type User = {
  id: number;
  name: string;
};

const user: User = { id: 1, name: "Taro" };

const userResult = logAndReturn<User>(user);
// T = User
// valueの型はUser
// 戻り値の型もUser
TypeScript

重要ポイント(ここをしっかり押さえておくと一気に楽になります):

  • 関数の中身はまったく同じでも、 呼び出し時に「Tに何を入れるか」を変えることで、 文字列・数値・オブジェクトなど、いろいろな型に対応できます。
  • しかも、戻り値の型もちゃんとTになるので、 呼び出し側で「型安全に」その値を扱えます。

Step 4:型推論を使って、もっとシンプルに書いてみる

毎回 <string> や <User> と書くのは少し面倒なので、 TypeScriptの「型推論」に任せてみます。

const strResult2 = logAndReturn("Hello"); // Tはstringと推論される
const numResult2 = logAndReturn(99);      // Tはnumberと推論される
const userResult2 = logAndReturn({ id: 2, name: "Hanako" }); // Tは { id: number; name: string } と推論される
TypeScript

ポイント:

  • Genericsは「型を引数として扱える」仕組みですが、 実際のコードでは「型を明示することもできるし、推論に任せることもできる」柔らかい使い方ができます。
  • 初心者の方は、まず「推論に任せるスタイル」で慣れていくのがおすすめです。

Step 5:もう少し実用的な処理を足してみる

ただログを出して返すだけだと味気ないので、 「型に応じて少しだけ違う表示をする」バージョンも考えてみます。

typeof を使って軽く分岐する例

// 型に応じてログの出し方を少し変えるジェネリック関数
function describeAndReturn<T>(value: T): T {
  const typeOfValue = typeof value;

  if (typeOfValue === "string") {
    console.log("文字列です:", value);
  } else if (typeOfValue === "number") {
    console.log("数値です:", value);
  } else {
    console.log("オブジェクトなどの値です:", value);
  }

  return value;
}

// 使用例
describeAndReturn("Hello");          // 「文字列です: Hello」
describeAndReturn(123);              // 「数値です: 123」
describeAndReturn({ id: 1, name: "Taro" }); // 「オブジェクトなどの値です: { ... }」
TypeScript

注意ポイント:

  • 関数の型としては T を使っていますが、 実行時には typeof で「実際の中身」を見て分岐しています。
  • Genericsは「コンパイル時の型の話」ですが、 実行時の分岐と組み合わせることで、より柔軟な処理が書けます。

Day 22 練習テンプレート:汎用ログ関数+少し応用

最後に、今日の内容をひとつのファイルにまとめた練習テンプレートを載せます。 このままコピーして、型を増やしたり、処理内容を変えたりしながら、 「Genericsで型に強い汎用関数を書く」感覚を育ててみてください。

// day22-generics-basic.ts
// Day 22:Generics入門「汎用関数を作る」

// 1. シンプルなジェネリック関数
function logAndReturn<T>(value: T): T {
  console.log("値:", value);
  return value;
}

// 使用例
const strResult = logAndReturn("Hello Generics"); // Tはstring
const numResult = logAndReturn(42);               // Tはnumber
const objResult = logAndReturn({ id: 1, name: "Taro" }); // Tは { id: number; name: string }

// 2. 型に応じてログの出し方を変えるジェネリック関数
function describeAndReturn<T>(value: T): T {
  const typeOfValue = typeof value;

  if (typeOfValue === "string") {
    console.log("文字列です:", value);
  } else if (typeOfValue === "number") {
    console.log("数値です:", value);
  } else {
    console.log("オブジェクトなどの値です:", value);
  }

  return value;
}

// 使用例
describeAndReturn("Hello");
describeAndReturn(123);
describeAndReturn({ id: 2, name: "Hanako" });
TypeScript

Day 22のまとめ

Day 22では、

  • Genericsとは何か(「型のためのパラメータ」を持つ仕組み)
  • <T> を使ったGeneric Functionの基本形
  • 文字列・数値・オブジェクトを、ひとつのジェネリック関数で安全に扱う流れ
  • 型推論を活かして、呼び出し側をシンプルに書くスタイル
  • 実行時の分岐(typeof)と組み合わせて、少しだけ賢い汎用関数にするアイデア

を、具体的なコードとコメント付きで体験しました。

Genericsは、最初は少し抽象的に感じるかもしれませんが、 「型を後から差し込める」「同じロジックをいろいろな型で使い回せる」という感覚が掴めてくると、 一気にTypeScriptの世界が広がっていきます。


Day 22:Generics演習で「どんな配列でも扱える汎用関数」を作る

第4週のテーマであるGenerics(ジェネリクス)を、もう一歩踏み込んで練習していきます。 今回の演習では、次の4つの汎用関数を作ることが目標になります。

  • getFirst<T>()
  • getLast<T>()
  • reverse<T>()
  • findById<T>()

キーワードは変わらずこちらです。

  • Genericsとは
  • <T>
  • Generic Function
  • 型を引数として扱う

ここでは、「配列」を題材にして、 どんな型の配列でも安全に扱える関数を、ステップバイステップで組み立てていきます。

Genericsの基本イメージをもう一度整理する

「型のための変数」としての <T>

Genericsは、

「型を後から差し込めるようにする仕組み」

でした。

<T> は、そのための「型パラメータ」です。

function identity<T>(value: T): T {
  return value;
}
TypeScript
  • T は「型の名前」
  • value: T は「valueはT型ですよ」
  • : T は「T型を返しますよ」

という意味になります。

今回の演習では、この T を「配列の要素の型」として使っていきます。

Step 1:getFirst<T>() を作る

どんな配列でも「最初の要素」を返す汎用関数

まずは、配列の先頭要素を取り出す関数から始めます。

// 配列の最初の要素を返すジェネリック関数
function getFirst<T>(items: T[]): T | undefined {
  // items: T[] なので、要素の型はT
  if (items.length === 0) {
    // 要素がない場合はundefinedを返す
    return undefined;
  }
  return items[0]; // 最初の要素(T型)を返す
}
TypeScript

重要ポイント:

  • 引数 items の型は T[](T型の配列)です。
  • 戻り値の型は T | undefined にしています。
    • 配列が空の場合もありえるので、「見つからない可能性」を型で表現しています。

実際に使ってみる

const numbers = [10, 20, 30];
const firstNumber = getFirst(numbers); // Tはnumberと推論される

console.log("最初の数値:", firstNumber); // 10

const names = ["Taro", "Hanako", "Jiro"];
const firstName = getFirst(names); // Tはstringと推論される

console.log("最初の名前:", firstName); // "Taro"
TypeScript

配列の中身が数値でも文字列でも、 getFirst は「その配列の要素の型」をTとして扱ってくれます。

Step 2:getLast<T>() を作る

配列の「最後の要素」を返す汎用関数

次は、配列の末尾要素を取り出す関数です。

// 配列の最後の要素を返すジェネリック関数
function getLast<T>(items: T[]): T | undefined {
  if (items.length === 0) {
    return undefined;
  }
  return items[items.length - 1]; // 最後の要素(T型)を返す
}
TypeScript

こちらも同じく、

  • 引数は T[]
  • 戻り値は T | undefined

という形になっています。

実際に使ってみる

const numbers2 = [10, 20, 30];
const lastNumber = getLast(numbers2); // Tはnumber

console.log("最後の数値:", lastNumber); // 30

const names2 = ["Taro", "Hanako", "Jiro"];
const lastName = getLast(names2); // Tはstring

console.log("最後の名前:", lastName); // "Jiro"
TypeScript

ポイント:

  • getFirst と getLast は、どちらも「配列の要素の型」をTとして扱っています。
  • 関数の中身はシンプルですが、Genericsのおかげで「どんな型の配列にも使える」汎用関数になっています。

Step 3:reverse<T>() を作る

配列を「逆順」にした新しい配列を返す汎用関数

次は、配列を逆順に並べ替える関数です。

// 配列を逆順にした新しい配列を返すジェネリック関数
function reverse<T>(items: T[]): T[] {
  // 元の配列を壊さないように、コピーを作ってからreverseする
  const copied = [...items]; // スプレッド構文でコピー
  copied.reverse();          // 逆順に並べ替え
  return copied;             // T[] 型の配列を返す
}
TypeScript

重要ポイント:

  • 引数も戻り値も T[] です。
  • 元の配列を直接 reverse() せず、コピーしてから操作しているのは、 「副作用を減らす」ためのちょっとした工夫です。

実際に使ってみる

const numbers3 = [1, 2, 3, 4];
const reversedNumbers = reverse(numbers3); // Tはnumber

console.log("元の配列:", numbers3);          // [1, 2, 3, 4]
console.log("逆順の配列:", reversedNumbers); // [4, 3, 2, 1]

const names3 = ["A", "B", "C"];
const reversedNames = reverse(names3); // Tはstring

console.log("元の配列:", names3);          // ["A", "B", "C"]
console.log("逆順の配列:", reversedNames); // ["C", "B", "A"]
TypeScript

Genericsのおかげで、 「数値の配列」「文字列の配列」「オブジェクトの配列」など、 どんな配列でも同じ関数で扱えるようになっています。

Step 4:findById<T>() を作る

「idプロパティを持つオブジェクト配列」から、指定IDの要素を探す

最後は少しだけ応用編です。

id プロパティを持つオブジェクトの配列から、 指定したIDの要素を探す汎用関数

を作ってみます。

ここでは、Genericsに「制約」をつける書き方を使います。

// idプロパティを持つ型のための制約
type WithId = {
  id: number;
};

// idで要素を検索するジェネリック関数
function findById<T extends WithId>(items: T[], id: number): T | undefined {
  // Tは「id: numberを必ず持っている型」であることが保証される
  return items.find((item) => item.id === id);
}
TypeScript

深掘りポイント:

  • T extends WithId という書き方は、 「Tは必ず id: number を持っている型ですよ」という制約を意味します。
  • これにより、関数の中で item.id を安全に使えるようになります。
  • 「idを持たない型」は、この関数に渡せなくなります。

実際に使ってみる

type User = {
  id: number;
  name: string;
};

type Product = {
  id: number;
  title: string;
  price: number;
};

const users: User[] = [
  { id: 1, name: "Taro" },
  { id: 2, name: "Hanako" },
];

const products: Product[] = [
  { id: 10, title: "Book", price: 1200 },
  { id: 11, title: "Pen", price: 200 },
];

const foundUser = findById(users, 2);      // TはUser
const foundProduct = findById(products, 11); // TはProduct

console.log("見つかったユーザー:", foundUser);     // { id: 2, name: "Hanako" }
console.log("見つかった商品:", foundProduct);       // { id: 11, title: "Pen", price: 200 }
TypeScript

ポイント:

  • findById は「idを持つ型なら何でも使える」汎用関数になっています。
  • UserでもProductでも、同じ関数で検索できるのに、 戻り値の型はちゃんと User や Product として扱われます。
  • これが「型を引数として扱う」Genericsの強みです。

Day 22 練習テンプレート:4つのジェネリック関数をまとめて試す

最後に、今回の4つの関数をひとつのファイルにまとめたテンプレートを載せます。 このままコピーして、配列の中身を変えたり、型を増やしたりしながら、 Genericsの感覚を育ててみてください。

// day22-generics-exercise.ts
// Day 22 演習:getFirst / getLast / reverse / findById

// 1. getFirst<T>()
function getFirst<T>(items: T[]): T | undefined {
  if (items.length === 0) {
    return undefined;
  }
  return items[0];
}

// 2. getLast<T>()
function getLast<T>(items: T[]): T | undefined {
  if (items.length === 0) {
    return undefined;
  }
  return items[items.length - 1];
}

// 3. reverse<T>()
function reverse<T>(items: T[]): T[] {
  const copied = [...items];
  copied.reverse();
  return copied;
}

// 4. findById<T>()
type WithId = {
  id: number;
};

function findById<T extends WithId>(items: T[], id: number): T | undefined {
  return items.find((item) => item.id === id);
}

// ===== 使用例 =====

type User = {
  id: number;
  name: string;
};

type Product = {
  id: number;
  title: string;
  price: number;
};

const users: User[] = [
  { id: 1, name: "Taro" },
  { id: 2, name: "Hanako" },
];

const products: Product[] = [
  { id: 10, title: "Book", price: 1200 },
  { id: 11, title: "Pen", price: 200 },
];

// getFirst / getLast
console.log("最初のユーザー:", getFirst(users));
console.log("最後の商品:", getLast(products));

// reverse
console.log("ユーザー配列(逆順):", reverse(users));
console.log("商品配列(逆順):", reverse(products));

// findById
console.log("ID=2のユーザー:", findById(users, 2));
console.log("ID=11の商品:", findById(products, 11));
TypeScript

Day 22 演習のまとめ

今回の演習では、

  • T を「配列の要素の型」として扱うジェネリック関数の基本
  • getFirst<T>() / getLast<T>() / reverse<T>() で、 どんな型の配列でも安全に扱える汎用関数を作る流れ
  • T extends WithId という制約付きGenericsで、 「idを持つ型だけを受け付ける」findById<T>() を作る方法

を、具体的なコードとコメント付きで体験しました。

Genericsは、「同じロジックをいろいろな型で使い回す」ための強力な道具です。 今日の4つの関数をベースに、ぜひ自分なりに

  • filterByStatus<T>()
  • mapToNames<T>()

など、オリジナルのジェネリック関数も考えてみてください。 そうしていくうちに、「型を引数として扱う」感覚が、自然と手に馴染んでいきます。

タイトルとURLをコピーしました