175
170

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【TypeScript】Generics(ジェネリクス)について深く学んでみた

Last updated at Posted at 2022-01-25

今回は、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以外もとてもわかりやすく学びが多い書籍でした。

以上となります。
ご拝読有難うござました。

175
170
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
175
170

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?