【入門編】 ジェネリクスにおけるextends制約 – TypeScript実践ガイド

TypeScriptの「ジェネリクス」で型に「魔法の扉」をつける方法 ~ `extends` で、もっと安全で、もっと賢く!~

やっほー! TypeScriptの世界へようこそ! ✨

「型定義」って聞くと、なんだか難しそう…って尻込みしちゃう気持ち、とってもよくわかるよ。でもね、TypeScriptの型定義って、実は私たちのコードを「おっちょこちょい」から守ってくれる、すごく頼りになる相棒なんだ。

今日は、そんな型定義の中でも、ちょっとだけ「おっ?」って思わせる、でも使いこなせるとコードが劇的に変わる「ジェネリクス」の「`extends`制約」っていう技に、こっそり触れてみようと思うんだ。

「ジェネリクス」とか「`extends`」って聞くと、また難しそう…って思った? 大丈夫、大丈夫! 難しい言葉は一旦忘れて、まずは身近な例え話で、この「魔法の扉」の仕組みを一緒に見ていこう!

頼れる「道具箱」と「選ばれし道具」のお話

想像してみて。君はDIYが大好き! いろんなネジやドライバー、ペンチなんかが入った、立派な「道具箱」を持っているとしよう。

この「道具箱」、とっても便利なんだけど、一つだけ困ったことがある。
「あれ?このネジ、このドライバーで回せるかな…?」
「このペンチ、このワイヤー切れるかな…?」

って、間違った道具を手に取って、ネジ穴をダメにしちゃったり、ワイヤーを傷つけちゃったりすることが、たまにあるんだ。

そこで、君は考えた。
「よし、もっと賢い道具箱にしよう!」

どうやって?

それは、道具箱に「魔法の扉」をつけること。

この「魔法の扉」は、どんな道具でも受け入れるわけじゃない。
例えば、

  • 「ネジ回し専用の扉」:この扉は、ネジを回すためのドライバーしか通さない。ドライバーじゃないものを入れようとすると、「ピピピ!それは違うよ!」って教えてくれる。
  • 「ワイヤー切り専用の扉」:この扉は、ワイヤーを切るためのペンチしか通さない。ペンチじゃないものを入れようとすると、これもまた「ダメだよ!」って教えてくれる。

どうかな? この「魔法の扉」のおかげで、間違った道具を選んでしまうことがグッと減ったと思わない? これが、TypeScriptの「ジェネリクス」と「`extends`制約」のイメージなんだ。

コードで見てみよう! ~ `extends` は「〜の仲間だよ」って教えてくれる ~

じゃあ、この「魔法の扉」を、どうやってコードで表現するのか見てみよう。

まず、いろんな「道具」たちを型で表現してみるね。

// シンプルな「道具」の型たち
interface Screwdriver {
turn: () => void; // 回す機能
}

interface Pliers {
cut: () => void; // 切る機能
}

// さらに、ちょっと特別な「電動ドライバー」!
// これは「Screwdriver」の仲間であり、さらに「powerOn」機能も持っているとしよう。
interface ElectricScrewdriver extends Screwdriver {
powerOn: () => void;
}

ここで登場したのが、`extends` というキーワード!
`ElectricScrewdriver extends Screwdriver` って書いてあるのは、
「`ElectricScrewdriver` は `Screwdriver` の仲間(= `Screwdriver` が持っている `turn` 関数を必ず持っている)だよ。それに加えて、`powerOn` という特別な機能も持っているよ!」
っていう意味なんだ。

まるで、君の「電動ドライバー」が、普通のドライバーの機能に加えて、さらにパワフルな機能を持っている、って言ってるみたいだね。

「魔法の扉」を作ってみよう! ~ ジェネリクスと `extends` の出番 ~

さて、いよいよ「魔法の扉」を作る番だよ。
ここでは、どんな「道具」でも受け取って、その道具が持っている機能を使える「万能ツールボックス」みたいな関数を作ってみよう。

// 「T」は、どんな「道具」でも入ってくる「型パラメータ」だよ。
// そして、`: T extends Screwdriver` の部分が「魔法の扉」!
// 「T」は「Screwdriver」の仲間(Screwdriver を継承している型)でなければならない、という制約をつけているんだ。
function useScrewdriver(tool: T) {
console.log(“道具箱を開けます!”);
tool.turn(); // Screwdriver の仲間だから、必ず turn() は使える!
console.log(“道具箱を閉じます。”);
}

// さあ、色々な「道具」で試してみよう!

// 1. 普通のドライバー(Screwdriver)
const regularScrewdriver: Screwdriver = {
turn: () => console.log(“カリカリ…”),
};
useScrewdriver(regularScrewdriver); // これはOK!

// 2. 電動ドライバー(ElectricScrewdriver、Screwdriver を extends してる!)
const electricScrewdriver: ElectricScrewdriver = {
turn: () => console.log(“ウィーン!”),
powerOn: () => console.log(“電源オン!”),
};
useScrewdriver(electricScrewdriver); // これもOK!

// 3. ペンチ(Pliers) – Screwdriver じゃない!
const pliers: Pliers = {
cut: () => console.log(“チョキン!”),
};

// useScrewdriver(pliers); // !!! エラーになる! !!!
// 「Argument of type ‘Pliers’ is not assignable to parameter of type ‘Screwdriver’.」
// (「Pliers」型は「Screwdriver」型に代入できません、という意味だよ)

どうかな?
`useScrewdriver` 関数は、`T extends Screwdriver` っていう「魔法の扉」のおかげで、`Screwdriver` の仲間しか受け取らないようになったんだ。
だから、普通のドライバーも、電動ドライバーも、ちゃんと受け入れてくれる。

でも、ペンチみたいに `Screwdriver` の仲間じゃないものを渡そうとすると、TypeScriptが「おいおい、その道具はここでは使えないよ!」って、優しく(でも、はっきりと!)教えてくれるんだ。

この「教えてくれる」っていうのが、TypeScriptのすごいところ。
プログラムが動く前に、間違った使い方を未然に防いでくれるから、後で「なんで動かないんだ!」って頭を抱えることが、ぐっと減るんだよ。

`extends` 制約の「いいところ」まとめ

  • 型安全性の強化: 「この関数は、こういう種類のデータしか受け付けないよ!」って、扉を閉めることで、間違ったデータが流れ込むのを防ぐ。
  • コードの意図が明確に: 関数やクラスの「顔」を見ただけで、「あ、この関数は〇〇の仲間を扱うんだな」って、作者の意図が伝わりやすくなる。
  • より柔軟な実装: ただ「`Screwdriver` なら何でもいい」じゃなくて、「`Screwdriver` の機能は最低限持っていて、さらに独自の機能も持っているもの」っていう、ちょっとワガママ(でも、賢い!)な要求もできる。

さいごに ~ 「魔法の扉」は、君のコードを強くする ~

今日の話、ちょっと難しかったかな?
でも、この「`extends`制約」っていうのは、TypeScriptの「ジェネリクス」っていう強力な武器を、さらに使いこなすための「コツ」みたいなものなんだ。

身の回りの「道具箱」や「扉」に例えてみると、意外と身近なものに感じられたんじゃないかな?

最初は「なんだか難しそう…」って思っても、実際にコードを書いて、エラーが出て、それを修正していくうちに、きっと「なるほど!」って膝を打つ瞬間が来るはず。

この「魔法の扉」を上手に使うことで、君の書くコードは、もっと安全に、もっと賢く、そしてもっと楽しくなっていくはずだよ。

これからも、TypeScriptの不思議で面白い世界を、一緒に探検していこうね!
またね!👋

コメント

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