TypeScriptのジェネリクス型がやっと理解できたのでまとめてみる
最初は意味がわからなすぎたジェネリクス型ですが、
徐々に理解できてきた?ので自分なりにまとめておきます。
ジェネリクス型って何?
TypeScript では、関数やクラスをより「柔軟かつ型安全」に使うために、ジェネリクス型(Generics)を活用します。
なぜ柔軟なの?
const testString = (value: string): void => {
console.log(value);
};
上記のような型が固定化された関数の場合、string 型しか受け取れません。
これでは、汎用性に欠けます。
testString("Hello"); // OK
testString(123); // エラー: number型は受け取れない
では、ジェネリクス型を使うとどうなるのか
const testAnything = <T>(value: T): void => {
console.log(value);
};
ここで出てきた T は「呼び出し時に決まる型の箱」のようなもの。
引数に応じて、型が自動で推論されるようになります。↓
testAnything("こんにちは"); // T は string 型
testAnything(123); // T は number 型
testAnything({ name: "さとう" }); // T はオブジェクト型
型安全とは?
どんな型でも扱えるようにしようとすると、つい any を使いたくなります。
const getFirst = (arr: any[]): any => {
return arr[0];
};
でもこれでは型安全じゃないし、TypeScriptの意味がなくなります
anyを使いたくなる時も、ジェネリクスを使うことで、型安全性も担保できるようになります。
const getFirst = <T>(arr: T[]): T => {
return arr[0];
};
このようにすれば、配列の型に合わせて返り値の型も自動で決まります。
const names = ["Yamada", "Tanaka"];
const firstName = getFirst(names); // string 型になる!
const ages = [21, 34];
const firstAge = getFirst(ages); // number 型になる!
応用編:extends 制約で型をしぼる
今までの説明より、ジェネリクスのいいところは「何でも受け入れられる」ことになります。
だた、開発をしていく中では「特定の条件を満たした型だけ受け入れたい」こともあります。
そんなときに使うのが extendsです。
例えば、以下の場合だとnameの存在を保証できないのでエラーになります。
const sayHello = <T>(obj: T): void => {
// obj.name が存在する保証がないのでエラー
console.log("こんにちは、" + obj.name);
};
こんな時は以下のようにextends制約をすることで、nameの存在を保証することができます
const sayHello = <T extends { name: string }>(obj: T): void => {
console.log("こんにちは、" + obj.name);
};
nameは必ず含まれるよと明示することができ、エラーを防ぐことができます。
sayHello({ name: "佐藤", age: 30 }); // OK
sayHello({ age: 30 }); // エラー:name がない
最後に
T が出てきたときは「え?どっからでてきたんだ?」と思っていたのが、
今では「なんでも任せられる信頼できるやつ」に変わりました
今後は Partial や Pick、keyof などのユーティリティ型との組み合わせもまとめてきたいなと思ってます。(そのためのパート①)
最後まで、読んでいただきありがとうございました🙏