TypeScriptのジェネリックとは

ジェネリックとは、型を抽象化する機構を指す。

Typescriptでは、型引数を使用して、どんな型でも受け取れる関数やクラスを実現することができる。

例えば、TypescriptにはPartialという型がある。

type Partial<T>

Partialは与えられた型の全てのプロパティを省略可能にした、新しい型を返す。

<T>が型引数で、任意の型を渡せる。Tはジェネリック型とも呼ばれる。

ジェネリックを活用した実装例

下記のようなサンプルを書いてみた。

interface User {
	firstName: string
  lastName: string
  email: string
  isAdmin: boolean
}

const createUser = (properties: Partial<User>) => {
  const _default = <T> (value: T, default: T): T => {
    return value === undefined ? default : value
  }
  return {
  	firstName: _default(properties.firstName, 'TEST'),
    lastName: _default(properties.lastName, 'TEST'),
    email: _default(properties.email, 'test@test.com'),
    isAdmin: _default(properties.isAdmin, false)
  }
}

createUserで新しいUserを作成することができる。

createUserの引数はPartial<User>なので、Userのプロパティを全て渡す必要はない。emailの部分だけテストしたいときはemailだけ渡せばいい。

_defaultは、引数として渡されたPartial<User>にプロパティがなかったら第二引数のデフォルト値を返す関数。

これにより、最低限のプロパティでユーザーを量産できるようになった。

ジェネリックに対する型制約

ジェネリックでなんでもできるというわけではなく、下記のような場合はコンパイルエラーになる。

interface User {
	login()
}

const login => <T> (cls: T) => {
  cls.login()
}

Tがlogin()関数を持っているかどうか分からないためにエラーが起こる。

このような場合は以下のようにジェネリックに制約を与える。

const login => <T extends User> (cls: T) => {
  cls.login()
}

これでTがUserまたは、Userを継承したクラスであることをコンパイラに明示できる。