はじめに
ライブラリなどで雰囲気で使っていたジェネリクスですが、しっかりと理解しようと言うことで備忘録も兼ねて今回記事にすることにしました。主にTypeScript公式ドキュメント[1]を参考に学習していきます。コードも基本的に[1]から引用しています。
ジェネリクスってなに?
型引数を利用して、実際に利用されるまで型が確定しないクラスや関数などを実現するためのもの。TypeScript特有のものではなく、他の言語にもあります。
例として以下のような関数を考えます。この関数では引数はnumber
型しか渡すことは出来ません。
function identity(arg: number): number {
return arg;
}
[1]より引用
次にジェネリクスを使用した以下のような関数を考えます。この関数では型を引数としてとるため、number
型以外でも引数として渡すことが出来ます。
function identity<Type>(arg: Type): Type {
return arg;
}
[1]より引用
このようにジェネリクスを用いることで、ロジックは共通しているが異なる型を引数とするものを1つにすることが出来ます。今回は様々なジェネリクスの書き方を学んでいこうと思います。また、今回の例では違いますが、ジェネリクスの型引数名は一般的にT,Uなどが使われることが多いです。
Extends
extends
は型に制約をつけることがつけることが可能です。
type User = {
name: string;
age: number;
};
function f<T extends User>(arg: T): string {
return arg.name;
}
上の例ではUser
型しか受け付けないようになっています。
Utility Types
型から別の型を出してくれる型であり、型の世界の関数というイメージです。種類は少なくないですが、覚えられなくもない数です。下のコードはReadonly
を用いた使用例です。
type Person = {
surname: string;
middleName?: string;
givenName: string;
};
type ReadonlyPerson = Readonly<Person>;
Keyof Type Operator
オブジェクトタイプを受け取り、そのキーの文字列または数値リテラル結合を生成。
type Point = { x: number; y: number };
//P1とP2は同じ型
type P1 = keyof Point;
type P2 = "x" | "y";
Typeof Type Operator
変数またはプロパティの型を参照。基本的には他の型演算子と組み合わせて使用。
let s = "hello";
// let n: string と同じ
let n: typeof s;
function f() {return { x: 10, y: 3 };}
//関数が返すのは値なので値の型を参照するためにtypeofを使用
type P = ReturnType<typeof f>;
Indexed Access Types
プロパティや配列要素の型を参照する方法を提供。
下のコード例ではオブジェクトタイプに対して各キーのtypeを取り出しています。
type Person = { age: number; name: string; alive: boolean };
// type I1 = string | number;
type I1 = Person["age" | "name"];
// type I2 = string | number | boolean;
type I2 = Person[keyof Person];
下のコードは配列に対してnumberでアクセスすることでオブジェクトタイプの方を返しています。多次元配列のような書き方でオブジェクトタイプ内のキーの型を入手することも可能です。
const MyArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
];
// type Person = {name: string;age: number;};
type Person = (typeof MyArray)[number];
// type Age = number;
type Age = (typeof MyArray)[number]["age"];
Conditional Types
型世界の条件分岐。inferキーワードを用いて型推論することが可能、
下のコードのように参考演算子っぽく振る舞う。
type IsNumber<T> = T extends number ? true : false;
type T1 = IsNumber<1>; //type T1 = true
type T2 = IsNumber<"test">; //type T2 = false
配列の中身の型を型推論で推測する。
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
Mapped Types
型のfor文的なもの。オブジェクトのキーを文字列リテラルから取ってくる使い方が頻出。
type Language = "jp" | "en";
type T = {
[key in Language]: string;
};
Template Literal Types
型の世界のテンプレートリテラル。
type World = "world";
type Greeting = `hello ${World}`;
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
ただ感情的にこれを型でつかうの気持ち悪く感じてしまう。
まだ調べきれてない型の使い方がたくさんあるので調べたら追記します。
参考文献
[1]:Generics
[2]:サバイバルtypeScript
[3]:type-challenges をやる
[4]:【TypeScript】TypeScript の Generics 制約