TypeScriptインデックスシグネチャは実際にはどういう意味ですか?


8

私はしばらくTypeScriptを書いていて、インデックスシグネチャの意味について混乱しています。

たとえば、次のコードは有効です。

function fn(obj: { [x: string]: number }) {
    let n: number = obj.something;
}

しかし、基本的に同じことをするこのコードはそうではありません:

function fn(obj: { [x: string]: number }) {
    let p: { something: number } = obj;
}

これはバグですか?これの意図された意味は何ですか?

回答:


9

混乱するのは当然です。インデックスの署名にはいくつかの意味があり、どこでどのように質問するかによって少し異なる意味になります。

まず、インデックスシグネチャは、型で宣言されたすべてのプロパティに互換性のある型が必要であることを意味します

interface NotLegal {
    // Error, 'string' isn't assignable to 'number'
    x: string;
    [key: string]: number;
}

これは、インデックスシグネチャの定義的な側面です。これらは、さまざまなプロパティキーを持つオブジェクトを記述しますが、すべてのキーで一貫した型です。このルールは、プロパティが間接参照を介してアクセスされたときに不正なタイプが観察されるのを防ぎます。

function fn(obj: NotLegal) {
    // 'n' would have a 'string' value
    const n: number = obj[String.fromCharCode(120)];
}

次に、インデックスシグネチャにより、互換性のあるタイプのインデックスに書き込むことができます

interface NameMap {
    [name: string]: number;
}
function setAge(ageLookup: NameMap, name: string, age: number) {
    ageLookup[name] = age;
}

これは、インデックスシグネチャの主要な使用例です。いくつかのキーのセットがあり、キーに関連付けられた値を格納したいとします。

3番目に、インデックスシグネチャは、ユーザー特に要求するプロパティの存在意味します。

interface NameMap {
    [name: string]: number;
}
function getMyAge(ageLookup: NameMap) {
    // Inferred return type is 'number'
    return ageLookup["RyanC"];
}

のでx["p"]x.pJavaScriptで同じ行動を持って、活字体は同等に扱います:

// Equivalent
function getMyAge(ageLookup: NameMap) {
    return ageLookup.RyanC;
}

これは、TypeScriptが配列を表示する方法と一致しています。つまり、配列へのアクセスはインバウンドであると見なされます。非常に一般的には、使用可能な既知のキーのセットがあり、追加のチェックを行う必要がないため、インデックスシグネチャの人間工学にもなります。

interface NameMap {
    [name: string]: number;
}
function getAges(ageLookup: NameMap) {
    const ages = [];
    for (const k of Object.keys(ageLookup)) {
        ages.push(ageLookup[k]);
    }
    return ages;
}

ただし、インデックスシグネチャ、インデックスシグネチャを持つ型を宣言されたプロパティを持つ型に関連付ける場合、任意の不特定のプロパティが存在することを意味しません

interface Point {
    x: number;
    y: number;
}
interface NameMap {
    [name: string]: number;
}
const m: NameMap = {};
// Not OK, which is good, because p.x is undefined
const p: Point = m;

この種の割り当てが実際に正しいとは考えにくいです。

これは、{ [k: string]: any }anyそれ自体の特徴的な機能です。インデックスシグネチャを使用して、オブジェクトのあらゆる種類のプロパティを読み書きできますが、あらゆるタイプの代わりに使用することはできませんany

これらの動作はそれぞれ個別に非常に正当化できますが、全体として見ると、いくつかの不整合が観察可能です。

たとえば、これらの2つの関数は実行時の動作が同じですが、TypeScriptはそのうちの1つだけが誤って呼び出されたと見なします。

interface Point {
    x: number;
    y: number;
}
interface NameMap {
    [name: string]: number;
}

function A(x: NameMap) {
    console.log(x.y);
}

function B(x: Point) {
    console.log(x.y);
}
const m: NameMap = { };
A(m); // OK
B(m); // Error

全体として、型にインデックスシグネチャを記述するときは、次のようになります。

  • このオブジェクトを任意のキーで読み書きしても問題ありません。
  • 任意のキーを介してこのオブジェクトから特定のプロパティを読み取るとき、それは常に存在します
  • このオブジェクトには、型の互換性を保つために、文字通りすべてのプロパティ名が存在するわけではありません
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.