こんにちは! 株式会社アルシエで教育に関するサポートをしている岸本です。
今回のテーマは「TypeScriptパート3」です。
オブジェクトの型は何を使えばいいのか
オブジェクトとは、プリミティブ型以外のデータ型を総称してオブジェクトと言います。
空のオブジェクトに型を付与する方法はいくつかありますが、以下の2つの方法はお勧めできません。
// どんなものでも代入可能になる型です
let a: {} = {}
let a: {} = "あいうえお”
let a: {} = 3
// プリミティブ型の場合は、エラーが出ます
// 型が厳格になっているため、{}で定義した型よりも安全性が高くなります。
let b: object = 2 // number を型 object に割り当てることはできません
let b: object = true // boolean を型 object に割り当てることはできません。
// []だとエラーがでません
let b: object = []
以下のコードであれば、プリミティブ型と[ ]もエラーが出ます。
空のオブジェクトを使用する場合、どちらでも問題ありません。
// Record オブジェクトの型を定義するときに使われる
// stringの部分はa、numberの部分は1を指しています
let c: Record<string, number> = {
a: 1,
};
// stringの部分はa、numberの部分は1を指しています
let d: { [key: string]: number } = {
a: 1,
};
Type Alias
Type Aliasとは、型に名前をつけられます。
type Foo = {
a:string
}
const bar:Foo = {
a:"あいうえお"
}
Interface
Type Aliasと同様に、型に名前をつけられます。
// Type Aliasとは違い、型の名前の後の「=」がいらない
interface Foo {
a: string;
}
const bar:Foo = {
a:"あいうえお"
}
Intersection Types
Intersection Typesとは、型を組み合わせたりする事ができます。
type bar = {
a: number;
b: string;
};
type foo = {
c: boolean;
};
// barとfooの型が合わさってる
// BarFoo = {
// a: number;
// b: string;
// c: boolean;
// };
type BarFoo = bar & foo;
// 「bar」の「a」と「b」のプロパティと「foo」の「c」のプロパティどちらもないとだめ
const synthesis: BarFoo = {
a:1,
b:"",
c:true
};
Union Types
Union Typesとは、型の OR 条件を表現する事ができます。
type bar = {
a: number;
b: string;
};
type foo = {
c: boolean;
};
type BarFoo = bar | foo;
// 「bar」の「a」と「b」のプロパティ、または「foo」の「c」のプロパティのどちらかがあれば良いです
// どちらかがあれば問題ありませんが、両方がある場合でも問題ないです
const synthesis: BarFoo = {
a:1,
b:"",
};
Type AliasとInterface違い
Type AliasとInterfaceでは、定義できる型に違いがあります。
Interfaceはオブジェクトに対して型宣言するものであり、以下のコードでは使えません。
// type Alias
type Foo = number;
const bar: Foo = 2;
// interface
// interfaceは「=」を使って既存の型を代入することはできません
interface Foo = number; // ERROR
const bar: Foo = 2;
宣言のマージ
interfaceは同じ型の名前がある場合、一つの型として取り扱う事ができます。
type Aliasは同じ型の名前があるとエラーが出ます。
// type Alias
type Bar {
a: string;
}
type Bar {
b: string;
}
const bar: Bar = { a: "花", b: "" }; // 識別子 Bar が重複しています!とエラー
// interface
interface Bar {
a: string;
}
interface Bar {
b: string;
}
const bar: Bar = { a: "花", b: "" };
const bar: Bar = { a: "花" }; //「b」がない!とエラー
継承方法が違う
Type Aliasの場合は、Intersection Typesを使う事で継承が可能です。
// interface
interface Bar {
a: string;
}
// interfaceのBarをextendsで継承している
interface Foo extends Bar {
b: number;
}
const bar: Foo = { a: "花", b:3 };
プロパティのオーバーライドの違い
プロパティのオーバーライドとは、同じ名前のプロパティを持つ2つの型定義がある場合、後から定義された方の型定義で先に定義された方のプロパティを上書きすることを指します。
ただし、プロパティの型が一致しない場合は、コンパイルエラーが発生します。
プロパティのオーバーライドでのType AliasとInterfaceの違いは、interfaceの方が型チェックがより厳密であり、型エラーをより早く検出することができます。
// type Alias
type Bar = {
a: string;
};
type Foo = Bar & {
a: number;
};
// string かつ number型は成り立たないのでneverになる
const bar: Foo = { a: "花" }; // aはnever型/
// interface
interface Bar {
a: string;
}
// Fooの宣言のタイミングでエラーが出ている
// インターフェイス 'Foo' はインターフェイス 'Bar' を正しく拡張していません
// stringとnumberは互換性が無いので成り立たない
interface Foo extends Bar {
a: number;
}
Mapped Typesは使用可能か、不可能か?
Mapped Types(マップ型)とは、ある型をもとに新たな型を定義する際に利用される型のことをいいます。
Type Aliasは使えますが、Interface使えません!
例えば、以下のようにコードを定義することは可能ですが、「userAttributes」に入れる値が増えるたびに
コードを修正する必要があり、手間がかかります。
type userAttributes = "firstName" | "lastName";
type bar = {
firstName:string,
lastName:string
}
そのときに役立つのがMapped Typesです。
[key in userAttributes]: string;と記述する事で、先ほどと同じ定義になります。
type userAttributes = "firstName" | "lastName";
type Foo = {
[key in userAttributes]: string;
};
// type Foo = {
// firstName: string;
// lastName: string;
// }
Type AliasとInterfaceどちらを使うべきかについては、どちらでもいいです。
TypeScript公式にも、どちらを使ってもいいと書いています。
次回はTypeScriptパート4です!