Was bedeutet eigentlich eine TypeScript-Indexsignatur?

8

Ich habe eine Weile TypeScript geschrieben und bin verwirrt darüber, was eine Indexsignatur bedeutet.

Dieser Code ist beispielsweise legal:

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

Aber dieser Code, der im Grunde das Gleiche tut, ist nicht:

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

Ist das ein Fehler? Was ist die beabsichtigte Bedeutung davon?

Ryan Cavanaugh
quelle

Antworten:

9

Sie haben Recht, verwirrt zu sein. Indexsignaturen bedeuten ein paar Dinge, und sie bedeuten etwas andere Dinge, je nachdem, wo und wie Sie fragen.

Erstens implizieren Indexsignaturen, dass alle deklarierten Eigenschaften im Typ einen kompatiblen Typ haben müssen .

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

Dies ist ein Definitionsaspekt von Indexsignaturen - sie beschreiben ein Objekt mit unterschiedlichen Eigenschaftsschlüsseln, aber einem konsistenten Typ für alle diese Schlüssel. Diese Regel verhindert, dass ein falscher Typ beobachtet wird, wenn auf eine Eigenschaft über eine Indirektion zugegriffen wird:

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

Zweitens ermöglichen Indexsignaturen das Schreiben in einen beliebigen Index mit einem kompatiblen Typ .

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

Dies ist ein Schlüsselanwendungsfall für Indexsignaturen: Sie haben einen Satz von Schlüsseln und möchten einen dem Schlüssel zugeordneten Wert speichern.

Drittens implizieren Indexsignaturen die Existenz einer Eigenschaft, nach der Sie speziell fragen:

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

Da x["p"]und x.phaben identisches Verhalten in JavaScript, Typoskript behandelt sie äquivalent:

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

Dies steht im Einklang mit der Art und Weise, wie TypeScript Arrays anzeigt. Dies bedeutet, dass der Arrayzugriff als In-Bound angenommen wird. Es ist auch ergonomisch für Indexsignaturen, da Sie häufig über einen bekannten Schlüsselsatz verfügen und keine zusätzlichen Überprüfungen durchführen müssen:

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

Indexsignaturen bedeuten jedoch nicht , dass eine beliebige, unspezifische Eigenschaft vorhanden ist, wenn ein Typ mit einer Indexsignatur mit einem Typ mit deklarierten Eigenschaften verknüpft wird:

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;

Diese Art der Zuordnung ist in der Praxis sehr unwahrscheinlich!

Dies ist ein Unterscheidungsmerkmal zwischen { [k: string]: any }und anysich selbst - Sie Schreibeigenschaften irgendwelcher Art auf einem Objekt mit einem Index Signatur gelesen und kann, aber es kann nicht überhaupt wie anstelle jeglicher Art verwendet werden anykann.

Jedes dieser Verhaltensweisen ist für sich genommen sehr gerechtfertigt, aber insgesamt sind einige Inkonsistenzen zu beobachten.

Zum Beispiel sind diese beiden Funktionen hinsichtlich ihres Laufzeitverhaltens identisch, aber TypeScript betrachtet nur eine davon als falsch aufgerufen:

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

Wenn Sie eine Indexsignatur für einen Typ schreiben, sagen Sie insgesamt:

  • Es ist in Ordnung, dieses Objekt mit einem beliebigen Schlüssel zu lesen / schreiben
  • Wenn ich eine bestimmte Eigenschaft von diesem Objekt über einen beliebigen Schlüssel lese, ist sie immer vorhanden
  • Dieses Objekt hat aus Gründen der Typkompatibilität nicht buchstäblich jeden Eigenschaftsnamen
Ryan Cavanaugh
quelle