今回は、Genericsについてです。
Genericsとは?
// こんなやつ
type Box<T extends Sweets>
Genericsは、型の決定を遅らせることができます。
型を定義する時には、<T>
のようにして、「T型」をエイリアスとして、使うときに決定 できます。
// 定義をする時
interface Hoge<T> {
value: T; // この場合、Tで決定された型が valueの型になる
}
// 使用時
const Foo: Hoge<string> = {
value: 'valueの型はstring'
}
この型の決定を遅らせることの意味は非常に大きいです。
汎用性があがります。
変数のGenerics
Genericsを利用する型を宣言する場合、型名称に続いて、慣習的に「T / U / K」などの型エイリアス名称が利用されることがありますが、任意の名称でOKです。
ちなみに型エイリアスの略称については諸説あるそうですが、
Typeの「T」、Keyの「K」、Unknownの「U」、Elementの「E」
がよく利用されることが多いそうです。
使用時には、Genericsの型指定が必須です。
基本的な付与
interface Hoge<T> {
value: T
}
const hoge: Hoge = { value: 'ふー' }; // 使用時に型を指定していないので、error
const hoge1: Hoge<string> = { valuse: 'valueの型はstring' };
const hoge1: Hoge<number> = { valuse: 12345 };
Genericsの初期値
Genericsの型は、初期型を指定できます。
この場合、使用時に型の省略が可能です。
interface Hoge<T = string> {
value: T
}
const hoge: Hoge = { value: '初期値がstringだよ' };
const hoge1: Hoge<string> = { value: '同じくstringだよ' };
const hoge1: Hoge<number> = { value: 'errorだよ' }; // number型なのでerror
extendsによる制約
Genericsには、制約をつけることができます。
<T>
だけどなんでもかんでもだめだよという時に制約をつけます。
// Tを string か number に限定
interface Hoge<T extends string | number> {
value: T
}
const hoge: Hoge<string> = { value: 'ストリングです' };
const hoge: Hoge<number> = { value: 12345 };
const hoge: Hoge<boolean> = { value: false }; // string | number ではないので error
関数のGenerics
関数も同じく、関数宣言時にGenericsを使うことができます。
基本的な付与
function getHoge<T>(args: T) {
return { value: args }
}
// アロー関数
const getHoge = <T> (args: T) => {
return { value: args }
}
getHoge<string>('string')
関数のGenericsは型の指定が必須ではない
変数の場合は、利用時に型を指定しなければエラーとなりましたが、関数の場合は、利用時の型指定は必須ではなりません。
以下のようにしても推論結果を得ることができます。
const hoge = getHoge('string'); // { value: 'string' };
const hoge1 = getHoge(0); // { value: 0 };
const hoge2 = getHoge(false); // { value: false };
const hoge3 = getHoge(null); // { value: null };
アサーションによる明示的な型の付与
Nullable型などを直接適用したい場合、宣言時にアサーションを付与します。
const hoge = getHoge(false as boolean | null) // { value: boolean | null }
const hoge1 = getHoge<string | null>(null) // { value: string | null }
extendsによる制約
変数と同様に、extendsで限定的な型にできます。
function getHoge<T extends string>(args: T) {
return { value: args }
};
const hoge = getHoge(0); // この時点で「型が違うよー」と教えてくれる
const hoge1 = getHoge('string');
便利なケース
関数の引数を型安全にできます。
interface Args {
amount: number;
};
function getHoge<T extends Args>(args: Args) {
return { value: args.amount.toFixed(1) }
};
const hoge = getHoge({ amount: 0 });
const hoge1 = getHoge({ value: 0 }); // Args型を満たしていないため error
const hoge2 = getHoge({ amount: 'string' }); // amount が number ではないため error
Genericsは複数OK
関数の引数と同じく、 のようにGenericsも複数指定することができます。
keyofによるLookup
今回のケースは、第1引数がT、 第2引数がK(制限付き) とします。
そしてこのケースでは、第2引数のKはどんな制限かというと、T を制限とします。
function pick<T, K extends keyof T>(args: T, key: K) {
return args[key];
};
const obj = {
name: 'Tom',
amount: 0,
flag: false,
};
const value = pick(obj, 'name') // const value: string
const value1 = pick(obj, 'amount') // const value1: number
const value2 = pick(obj, 'flag') // const value2: boolean
const value3 = pick(obj, 'test') // error 'test' プロパティはない
クラスのGenerics
クラス宣言にGenericsを利用すると、コンストラクターの引数を制約できます 。
以下の例は、インスタンス生成時にstring型の引数を求めています。
class Person<T extends string> {
name: T
constructor (name: T) {
this.name = name;
};
};
const person = new Person('Tom');
const personName = person.name // const personName: 'Tom'
オブジェクトを引数にとる場合
オブジェクトを引数に取る場合、コンストラクターは「T extends PersonProps」という制約を設けて、クラスメンバーに「Indexed Access Types」を利用した型の付与が可能です。
interface PersonProps {
name: string
age: number
gender: 'male' | 'female' | 'other'
};
class Person<T extends PersonProps> {
name: T['name']
age: T['age']
gender: T['gender']
constructor (props: T) {
this.name = props.name
this.name = props.age
this.name = props.gender
}
}
const person = new Person({
name: 'Tom'
age: 20
gender: 'male'
})
参考文献
今回参考にさせて頂いたのは以下の書籍です。
今回のGenerics以外もとてもわかりやすく学びが多い書籍でした。
以上となります。
ご拝読有難うござました。