TypeScriptの型記法、「オブジェクトリテラル」と「インデックスシグネチャ」について、まとめます。
「オブジェクトリテラル」も「インデックスシグネチャ」も、あまり聞き慣れない言葉で、雰囲気で理解した気になっていませんか?
私は、なっていました。
私の場合、この記法に遭遇するたびに「これって、なんだっけ?」ってなることが多いです。
自分の理解のために、ここにまとめておきたいと思います。
誰かの理解の助けになれば幸いです。
初見殺しな「オブジェクトリテラル」と「インデックスシグネチャ」
例えば、以下のコードにおいて
let obj: {
[key: string]: string // インデックスシグネチャ
} // オブジェクトリテラル
変数objの型が、何を意味しているか理解できますでしょうか。
私には、初見殺しすぎて、さっぱりわからず「???」となりました。
この型の意味は、「変数objは複数のkeyを含む可能性があり、そのすべてのstring型のkeyは、string型の値をもつ」となります。
以下のコードにおいて、Errorの箇所では、idにnumber型の値を入れているのでエラーとなります。
let obj: {
[key: string]: string // インデックスシグネチャ
} // オブジェクトリテラル
// OK
obj = {
id: "123"
token: "hogetoken"
name: "fuganame"
}
// Error
// Type 'number' is not assignable to type 'string'.
obj = {
id: 123
token: "hogetoken"
name: "fuganame"
}
オブジェクトリテラルとは
let obj: {
id: string
token: string
name: string
} // オブジェクトリテラル
このように中括弧({})を用いて、オブジェクトの型を定義する記法のことを指します。
オブジェクトは、ハッシュテーブルの一種で、キーと値の組みを持ちます。
TypeScriptでは、多くの場合、オブジェクトの型は「クラス」を用いて定義されます。
しかし、用途によってはクラスを定義せずに、即席の型定義を記述する方が便利な場合があります。
その際に用いられるのが「オブジェクトリテラル」です。
「オブジェクトリテラル」は、匿名のオブジェクト型を宣言したい場合に用いられます。
(例えばJavaScriptからTypeScriptへの移行期に、"とりあえず"の型を定義したい場合など)
インデックスシグネチャとは
let obj: {
// [key: T]: U
[key: string]: string // インデックスシグネチャ
}
インデックスシグネチャは、角括弧([])を用いて「[key: T]: U」という構文で記述されます。
これは「オブジェクトが、複数のkey(型T)を含む可能性があること」を示します。
そして、「すべてのkey(型T)は型Uの値を持つこと」も示します。
オブジェクトのキーと値の型を抽象化して記述することができます。
備考
例えば、idだけnumber型で、その他のパラメータはstringの場合、以下のように書けるのかなと思ったのですが、これはエラーになります。
let obj: {
id: number // エラー
[key: string]: string
}
// Property 'id' of type 'number' is not assignable to string index type 'string'.
// Type '{ id: number; token: string; name: string; }' is not assignable to type
// '{ [key: string]: string; id: number; }'. Property 'id' is incompatible with
// index signature. Type 'number' is not assignable to type 'string'.
obj = {
id: 123
token: "hogetoken"
name: "fuganame"
}
keyがstringの場合は、stringの値を持つと宣言してしまっているので、
[key: string]: string
idをnumber型だと定義すると矛盾が発生してしまうためです。
id: number
[追記] 以下のように型交差記法を用いてもエラーが発生します。
(親切な方、教えていただいてありがとうございます)
// エラー
const obj: { [key: string]: string } & { id: number } = {
id: 123,
token: "hogetoken",
name: "fuganame"
};
よって、この場合はおとなしく、インデックスシグネチャを用いないで書きましょう。
let obj: {
id: number
token: string
name: string
} // オブジェクトリテラル
まとめ
・オブジェクトリテラル({})は、オブジェクトの型を即席で定義するために用いられる。
・インデックスシグネチャ([key: T]: U)は、オブジェクトが、複数のkey(型T)を含む可能性があり値はU型になることを示している。
結論としては、オブジェクトリテラルもインデックスシグネチャもクセが強いので、あまり用いたくないですが、そうは言っても現場ではすでにオブジェクトリテラルやインデックスシグネチャを使用している箇所については、コードを読むために理解しておきたいと思います。
[追記]「オブジェクトリテラル」と「オブジェクト型リテラル」の違いについて
コメント欄にて、下記のご指摘を頂きました。
ありがとうございます。
細かい点ですが、「オブジェクトリテラル」はオブジェクト値を構築する記法のことで、 JavaScript/TypeScript に共通しています。この記事で扱っている、オブジェクトの型を記述する TypeScript 特有の記法は「オブジェクト型リテラル」です
const obj: { n: number } = { n: 42 };
// ^^^^^^^^^^^^^ ^^^^^^^^^---- オブジェクトリテラル
// `---------------------- オブジェクト型リテラル
「オブジェクトリテラル」と「オブジェクト型リテラル」の違いについて、理解しました。
この違いはちゃんと理解しておく必要があると感じましたので引用させていただきます。