【TypeScript】型定義(Type Arias)
Type Arias とは
TypeScript では、ユニオン型やリテラル型、オブジェクトなどさまざまな型の宣言方法があります。
const union: string | number | null = 0
const reteral: 'Taro' | 'Jiro' | 'Saburo' = 'Taro'
const user: {
id: number
name: string
} = {
id: 1,
name: 'Taro',
}
しかし、これらを何度も使用するときに同じ宣言をするのは、非常に面倒です。
そのような時に使用するのが、Type Arias です。
これは型の宣言に対し、type
を用いることで名前付けをすることができます。
type Union = string | number | null
const union: Union = 0
type Reteral = 'Taro' | 'Jiro' | 'Sabro'
const reteral: Reteral = 'Taro'
type User = {
id: number
name: string
}
const user: User = {
id: 1,
name: 'Taro',
}
オブジェクト定義
オブジェクトの定義について、少しだけ補足をしておきます。
任意プロパティ
通常、オブジェクトで定義したプロパティは必須ですが、?
を付けることで任意にすることができます。
type User = {
id: number
name?: string
}
const user1: User = {
//OK
id: 1,
name: 'Taro',
}
const user2: User = {
//OK
id: 1,
}
readonly
readonly
を付けることで、読み取り専用のプロパティとすることができます。
type User = {
readonly id: number
name: string
}
const user: User = {
id: 1,
name: 'Taro',
}
console.log(user.id) //OK
user.id = 2 //NG
オブジェクトを入れ子にする場合、readonly
したオブジェクトのプロパティまでは有効にならないことに注意してください。
type ExUser = {
readonly user: User
age: number
}
const exUser: ExUser = {
user: {
id: 1,
name: 'Taro',
},
age: 10,
}
exUser.user.id = 2 //NG: Userでreadonlyとしているため
exUser.user.name = 'Jiro' //OK
exUser.user = {
//NG: ExUserでreadonlyとしているため
id: 2,
name: 'Jiro',
}
インデックス型
オブジェクトのキーは何でもよく、値の型のみ指定する場合に使用します。
type T = {
[key: string]: string
}
上記の例であれば、string
の値であればどのようなキーでも設定できます。
const strs: T = {
a: 'A',
b: 'B',
c: 'C',
}
[]
の部分にはstring
かnumber
のみ設定できます。
number
を設定した場合は、配列のように扱うことができます。
実際にはarray
型ではないので、length
などのプロパティは使用できません。
type T = {
[key: number]: string
}
const nums: T = ['A', 'B', 'C']
console.log(nums.length) //NG
プロパティの型参照
型[キー]
とすることで、プロパティの型を参照することができます。
type User = {
id: number
name: string
}
type T1 = User['id'] //number
type T2 = User['name'] //string
ユニオン型
Type Arias でも、通常の型と同様にユニオン型を使用することができます。
type A = string | undefined
type B = number | undefined
type C = A | B // string | number | undefined
オブジェクトをユニオン型で指定する場合は、以下のようにプロパティがマージされます。
type A = {
id: number
}
type B = {
name: string
age: number
}
type User = A | B
const user: User = {
id: 1,
name: 'Taro',
age: 20,
}
すべてのプロパティを持つオブジェクトを指定する必要はなく、いずれかの型を満たすオブジェクトを指定すればよいです。
const user1: User = { id: 1 } //OK
const user2: User = { name: 'Taro', age: 20 } //OK
const user3: User = { name: 'Taro' } //NG
型の違う同じプロパティ名を持っていた場合はマージされます。 つまり、どちらの型も持つユニオン型となります。 ただし、上記で説明した通り、いずれかの型を満たさなければエラーとなります。
type A = {
id: number
name: string
}
type B = {
id: string
age: number
}
type User = A | B
const user1: User = { id: 1, name: 'Taro', age: 20 } //OK
const user2: User = { id: '1', name: 'Taro', age: 20 } //OK
const user3: User = { id: '1', name: 'Taro' } //NG
インターセクション型
インターセクション型は、型の合成を意味します。 ユニオン型はいずれかの型を満たせばよかったですが、インターセクション型はすべての型を満たす必要があります。
type A = {
id: number
}
type B = {
name: string
age: number
}
type User = A & B
const user: User = {
id: 1,
name: 'Taro',
age: 20,
}
const user1: User = { id: 1 } //NG
const user2: User = { name: 'Taro', age: 20 } //NG
const user3: User = { name: 'Taro' } //NG
同じキーがある場合、型が違うとエラーになります。
type A = {
id: number
name: string
}
type B = {
id: string
age: number
}
type User = A & B //NG
keyof
keyof
は、オブジェクト型のキーをリテラル型として型定義することができます。
type User = {
id: number
name: string
age: number
}
type UserKey = keyof User // 'id' | 'name' | 'age'
const key1: UserKey = 'id' //OK
const key2: UserKey = 'gender' //NG
typeof
と組み合わせることで、オブジェクトからキーの型定義をすることができます。
const obj = {
hoge: 'A',
fuga: 'B',
piyo: 'C',
}
type Keys = keyof typeof obj // 'hoge' | 'fuga' | 'piyo'
Mapped Types
Mapped Types は、オブジェクトを以下の構文によって生成する機能です。
{ [P in K]: T }
K
はキーの集合体で、string
のリテラル型である必要があります。
T
はプロパティの型となります。
まずは簡単な例を見てみます。
type T = {
[P in 'x' | 'y']: number
}
//以下と同義
type T = {
x: number
y: number
}
このようにリテラル型を使用することで、プロパティが展開されます。
keyof
を使用することで、既存のオブジェクト型から型を定義することができます。
type Org = {
x: string
y: number
}
//Orgと同じ型定義が得られる
type T = {
[P in keyof Org]: Org[P]
}
また?
やreadonly
を付与することが可能です。
type T = {
readonly [P in keyof Org]?: Org[P]
}
//以下と同義
type T = {
readonly x?: string
readonly y?: number
}