TypeScript サブタイプとスーパータイプ
TypeScriptの型同士の関係には、サブタイプ(subtype)とスーパータイプ(supertype)という概念がある。
型Aと型Bが存在し、型Aの代わりに型Bを使用することができるとき(型Aが求められる文脈で型Bが使用できるとき)、型Bをサブタイプ、型Aをスーパータイプと呼ぶ。一般的に、サブタイプとスーパータイプの関係は記号<:を用いて、「サブタイプ <: スーパータイプ」と表現する。この表記法に従えば、型Bと型Aの関係はB<:Aと表現できる。
// 変数はvalは型推論により string 型になる
const val = '10'
// number | string 型が求められる変数に string 型の変数を代入
const age: number | string = val // OK 上記の例では、変数ageはnumber | string型が指定されているが、代わりにstring型の変数を代入している。この操作は許可される。この場合、string型はnumber | string型のサブタイプで、逆にnumber | string型はstring型のスーパータイプ。
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' では必須です。 上記の例では、関数logNameはName型のオブジェクトを引数にとり、logNameAndAgeはNameAndAge型のオブジェクトを引数にとる。
logName関数にNameAndAge型のオブジェクトを渡すとことができるのは、NameAndAge型がName型のサブタイプであるため(NameAndAge<:Name の関係)。この場合、logName関数はnameプロパティのみを使用しているため、追加のageプロパティがあっても問題ない。
一方で、logNameAndAge関数にName型のオブジェクトを渡すことはできない。これはName型がageプロパティを持てないため、NameAndAge型のスーパータイプとみなされず、必要なプロパティが不足しているため。
部分集合に属するすべての要素は、その上位集合にも属する。すなわち、サブタイプに含まれるすべての値はスーパータイプの値として代わりに使用できるとということが理解できる。