Index signature(インデックス型)

JavaScript(TypeScript)のObjectは、他のJavaScriptオブジェクトへの参照を保持し、文字列でアクセスできます。

簡単な例:

let foo:any = {};
foo['Hello'] = 'World';
console.log(foo['Hello']); // World

キー"Hello"に文字列"World"を格納します。JavaScriptオブジェクトを保存できると言ったので、クラスインスタンスを保存してみましょう:

class Foo {
  constructor(public message: string){};
  log(){
    console.log(this.message)
  }
}

let foo:any = {};
foo['Hello'] = new Foo('World');
foo['Hello'].log(); // World

また、stringでアクセスできると言いましたね。もし他の何らかのオブジェクトをインデックスシグネチャに渡すと、JavaScriptのランタイムは.toStringを事前に呼び出し、その文字列を得ます。これは以下の通りです:

let obj = {
  toString(){
    console.log('toString called')
    return 'Hello'
  }
}

let foo:any = {};
foo[obj] = 'World'; // toString called
console.log(foo[obj]); // toString called, World
console.log(foo['Hello']); // World

インデックスを示す場所でobjが使われるたびにtoStringが呼び出されることに注意してください。

配列は若干異なります。number型のインデックスを使うと、JavaScript VMは、最適化を試みます(それが実際に配列であるか、格納された要素の構造がすべて一致しているかといったことに依存します)。なのでnumberはそれ自身、stringとは別の、正しいオブジェクトアクセサとみなされるべきです。以下は単純な配列の例です:

それがJavaScriptです。ではTypeScriptでの優雅な取り扱い方を見ていきましょう。

TypeScriptのインデックスシグネチャ

JavaScriptはインデックスシグネチャにオブジェクトを使った場合、暗黙的にtoStringを呼び出します。そのため、TypeScriptは、初心者が落とし穴にはまるのを防ぐために、エラーを出します(私はいつもstackoverflowで落とし穴にはまるJavaScriptユーザーをたくさん見ています):

ユーザーに明示的にtoStringを使うことを強制する理由は、オブジェクトのデフォルトのtoString実装がかなりひどいためです。v8では常に[object Object]を返します:

もちろんnumberはサポートされています。理由は以下の通りです。

  1. すばらしい配列/タプルのサポートに必要です。

  2. あなたがobjとしてそれを使うとしても、デフォルトのtoString実装はまともです([object Object]ではありません)。

ポイント2を以下に示します。

だから、レッスン1:

TypeScriptのインデックスシグネチャはstringまたはnumberのいずれかでなければなりません。

参考まで: symbolsもまたTypeScriptでサポートされています。しかし、まだそこには行かないでください。小さな一歩からです。

インデックスシグネチャを宣言する

今まで私たちは、TypeScriptに私たちが望むことをさせるためにanyを使ってきました。私たちは、実際にはインデックスシグネチャを明示的に指定できます。例えば文字列を使ってオブジェクトに格納されているものが構造体{message: string}に従っていることを確認したいとします。これは{ [index:string] : {message: string} }の宣言で行うことができます。これは以下のとおりです:

ヒント: インデックスシグネチャの名前{ [index:string] : {message: string} }indexはTypeScriptにとっては意味がなく、可読性のためだけのものです。例えばもしそれがユーザー名であれば、コードを見る次の開発者のために{ [username:string] : {message: string} }と宣言することができます。

もちろん、numberインデックスもサポートされています。例:{ [count: number] : SomeOtherTypeYouWantToStoreEgRebate }

すべてのメンバはstringインデックスシグネチャに従わなければならない

stringインデックスシグネチャを持つと、それと同時に、すべての明示的なメンバもそのインデックスシグネチャに準拠している必要があります。これを以下に示します:

これは、すべての文字列アクセスで同じ結果が得られるように安全性を提供するためです:

限られた個数の文字列リテラルを使う

マップ型(Mapped Types)を使うことで、インデックス文字列が「リテラル文字型のユニオン型(Union)」のメンバであることを要求するようなインデックスシグニチャを作ることができます。

辞書の語彙(vocabulary)の型を得るために、次のページで解説するkeyof typeofがしばしば共に使われます。

語彙の指定はジェネリクスを使うことで後回しにできます。

stringnumberインデクサの両方を持つ

これは一般的な使用例ではありませんが、TypeScriptコンパイラはこれをサポートしています。

しかし、stringインデクサはnumberインデクサよりも厳格であるという制約があります。これは意図的なものです。次のようなコードを可能にします:

デザインパターン:ネストされたインデックスシグネチャ

インデックスシグネチャを追加する際のAPIの考慮事項

JSコミュニティでは、通常、文字列インデクサを乱用するAPIをよく見かけるでしょう。例えばJSライブラリにおけるCSSの共通パターンです:

このように、文字列インデクサと有効な値を混在させないようにしてください。例えばオブジェクトに入れるときのタイプミスはキャッチされません:

代わりに、ネストを独自のプロパティに分離します。例えば、nest(またはchildrensubnodesなど)のような名前で宣言します:

インデックスシグネチャから特定のプロパティを除外する

場合によっては、プロパティを結合してインデックスシグニチャにしたいことがあります。これは推奨されておらず、上記のネストされたインデックスシグネチャのパターンを使用するべきです。

ただし、既存のJavaScriptをモデリングしている場合は、交差型(intersection type)で回避することができます。次に、交差型を使用せずに発生するエラーの例を示します。

交差型を使用した場合の回避策は次のとおりです。

あなたはそれを既存のJavaScriptオブジェクトに対して宣言することができますが、そのようなオブジェクトをTypeScriptで生成できないことに注意してください。

最終更新

役に立ちましたか?