TypeScript 型同士の関係
型とはデータを分類した時の種類のこと。データを分類するという行為は、すべてのデータの集まりの間に線を引いて、一部のまとまりを、共通の特徴を持つ「型」として区別していく作業。
型の場合は、分類の対象がデータの集まりだが、一般的に「もの」の集まりは集合という概念で定義することができ、集合を構成する個々のものは要素と呼ばれる。
数値リテラル型のユニオン型
// 特定の数値のみをメンバーにもつユニオン型を定義
type JpnCoin = 1 | 5 | 10 | 50 | 100 | 500
type UsCoin = 1 | 5 | 10 | 25 上記の例では、特定の数値リテラル型をメンバーに持つユニオン型を2つ定義している。それぞれのユニオン型は、限定された数値だけを要素とする集合とみなせる。たとえばJpnCoin型は、1、5、10、50、100、500という特定の数値リテラル型を要素としてもち、JpnCoin型に属すると同時に、より広い範囲のnumber型にも属するという性質を持つ。
以上のことから、特定のリテラルのみを含むユニオン型は、number型の集合内に、別の小さな集合を形成していると言える。このように、ある集合Aの中に、特定の要素だけを持つ小さな集合Bが存在する時、その集合Bを集合Aの部分集合(subset)と呼ぶ。一方、この時の集合Aは集合Bの上位集合(superset)という。JpnCoin型とUsCoin型は、どちらもnumber型の部分集合に該当する。
型(集合)同士の演算
type JpnCoin = 1 | 5 | 10 | 50 | 100 | 500
type UsCoin = 1 | 5 | 10 | 25
// 集合同士の演算
// ユニオン型
type UnionCoin = JpnCoin | UsCoin
// 100 | 1 | 5 | 10 | 50 | 500 | 25
// インターセクション型
type IntersectionCoin = JpnCoin & UsCoin
// 1 | 5 | 10 上記の例では、UnionCoin型は、JpnCoinとUsCoinを包含するより大きな集合となり、IntersectionCoin型はJpnCoinとUsCoinの共通部分を表す部分集合として現れる。
異なるプリミティブ型同士の演算
// ユニオン型
type NumberOrString = number | string
// インターセクション型
type NumberAndString = number & string // never 上記の例ではNumberOrString型は、number型の集合とstring型の集合を合体させた集合であり、これによって数値と文字列の両方を要素として包む集合が形成される。対照的に、NumberAndString型はnumber型とstring型の両方の属性を同時に持つ要素の集合を示すが、そのような要素は実際に存在しないため、この型はからの集合を表す。空の集合は空集合と呼ばれ、TypeScriptでは、never型として扱われる。空集合は定義上、すべての集合の部分集合となる。
オブジェクト型と集合
// string型のnameプロパティを持つオブジェクト型を定義
type Name = {
name: string
}
let john: Name
const objA = { name: 'John' }
john = objA // OK. nameプロパティが存在するため
const objB = { name: 'John', gender: 'male' }
john = objB // OK. name以外のプロパティが含まれていても代入可能。つまり、objBはName型の要素。
const objC = {
fullName: 'John Doe',
age: 25,
}
// john = objC
// プロパティ 'name' は型 '{ fullName: string; age: number; }' にありませんが、型 'Name' では必須です。 上記の例では、3種類のオブジェクトを定義してそれぞれName型の変数johnに代入している。
1つ目の
objAはstring型のnameプロパティのみを持つオブジェクト。Name型の要素なので代入可能。2つ目の
objBはname以外のプロパティを持つが、条件を満たすのでこのオブジェクトも代入可能。objBはnameプロパティに加えて追加のプロパティを持っているが、Name型の要件を満たしているため代入は許される。ただし、過剰プロパティチェックにより、オブジェクトリテラルを直接変数に代入するとエラーが発生するので注意。3つ目の
objCは必要なnameプロパティを持っていないため、代入は型エラーになる。
オブジェクト型同士のユニオン型の変数とオブジェクトの代入
type Name = {
name: string
}
type Age = {
age: number
}
// ユニオン型を定義
type NameOrAge = Name | Age
// { name: string } | { age: number }
let john: NameOrAge
john = { name: 'John' } // OK
john = { age: 20 } // OK 上記の例では、number型のageプロパティを持つAge型を定義している。次に、NameOrAge型として、Age型とName型のユニオン型を定義して、その型を変数johnに指定する。
johnは、string型のnameプロパティを持つオブジェクト、あるいはnumber型のageプロパティを持つオブジェクトのいずれかを受け入れることができる。両方のプロパティを持つオブジェクトや、追加のプロパティを含むオブジェクトも代入可能。
オブジェクト型同士のインターセクション型の変数とオブジェクトの代入
type Name = {
name: string
}
type Age = {
age: number
}
// インターセクション型
type NameAndAge = Name & Age
// { name: string; age: number }
let alice: NameAndAge
alice = {
name: 'Alice',
age: 30,
}
// alice = {
// name: 'Alice'
// }
// 型 '{ name: string; }' を型 'NameAndAge' に割り当てることはできません。
// プロパティ 'age' は型 '{ name: string; }' にありませんが、型 'Age' では必須です。 上記の例では、Name型とAge型の属性を組み合わせたインターセクション型NameAndAge型を定義。この型の変数aliceには、string型のnameプロパティとnumber型のageプロパティの両方を含んだオブジェクトのみが代入可能。nameプロパティのみを含むオブジェクトは、ageプロパティが欠けているため代入できない。