Skip to content

TypeScript サブタイプとスーパータイプ

TypeScriptの型同士の関係には、サブタイプ(subtype)とスーパータイプ(supertype)という概念がある。

 型Aと型Bが存在し、型Aの代わりに型Bを使用することができるとき(型Aが求められる文脈で型Bが使用できるとき)、型Bをサブタイプ、型Aをスーパータイプと呼ぶ。一般的に、サブタイプとスーパータイプの関係は記号<:を用いて、「サブタイプ <: スーパータイプ」と表現する。この表記法に従えば、型Bと型Aの関係はB<:Aと表現できる。

ts
// 変数はvalは型推論により string 型になる
const val = '10'

// number | string 型が求められる変数に string 型の変数を代入
const age: number | string = val // OK

 上記の例では、変数agenumber | string型が指定されているが、代わりにstring型の変数を代入している。この操作は許可される。この場合、string型はnumber | string型のサブタイプで、逆にnumber | string型はstring型のスーパータイプ。

ts
type Name = {
  name: string
}

// Name型のサブタイプ
type NameAndAge = {
  name: string
  age: number
}

// nameだけを出力する関数
function logName(person: Name) {
  console.log(person.name)
}

// nameとageを出力する関数
function logNameAndAge(person: NameAndAge) {
  console.log(person.name, person.age)
}

const personOnlyName: Name = { name: 'John' }
const personAndAge: NameAndAge = { name: 'John', age: 20 }

// OK
logName(personAndAge)
// NG
// logNameAndAge(personOnlyName)
// 型 'Name' の引数を型 'NameAndAge' のパラメーターに割り当てることはできません。
// プロパティ 'age' は型 'Name' にありませんが、型 'NameAndAge' では必須です。

 上記の例では、関数logNameName型のオブジェクトを引数にとり、logNameAndAgeNameAndAge型のオブジェクトを引数にとる。

logName関数にNameAndAge型のオブジェクトを渡すとことができるのは、NameAndAge型がName型のサブタイプであるため(NameAndAge<:Name の関係)。この場合、logName関数はnameプロパティのみを使用しているため、追加のageプロパティがあっても問題ない。

 一方で、logNameAndAge関数にName型のオブジェクトを渡すことはできない。これはName型がageプロパティを持てないため、NameAndAge型のスーパータイプとみなされず、必要なプロパティが不足しているため。

 部分集合に属するすべての要素は、その上位集合にも属する。すなわち、サブタイプに含まれるすべての値はスーパータイプの値として代わりに使用できるとということが理解できる。