Wie kann ich verhindern, dass beim Kompilieren von Typoskript mit aktiviertem noImplicitAny-Flag der Fehler "Indexsignatur des Objekttyps hat implizit einen 'any'-Typ" auftritt?


309

Ich kompiliere Typescript immer mit dem Flag --noImplicitAny. Dies ist sinnvoll, da meine Typprüfung so streng wie möglich sein soll.

Mein Problem ist, dass ich mit dem folgenden Code den Fehler bekomme Index signature of object type implicitly has an 'any' type:

interface ISomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   string;
}

let someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue'
};

let key: string = 'secondKey';

let secondValue: string = someObject[key];

Es ist wichtig zu beachten, dass die Schlüsselvariable von einer anderen Stelle in der Anwendung stammt und jeder der Schlüssel im Objekt sein kann.

Ich habe versucht, den Typ explizit umzuwandeln durch:

let secondValue: string = <string>someObject[key];

Oder ist mein Szenario mit einfach nicht möglich --noImplicitAny?

Antworten:


337

Durch Hinzufügen einer Indexsignatur wird TypeScript über den Typ informiert.

In Ihrem Fall wäre das [key: string]: string;

interface ISomeObject {
    firstKey:      string;
    secondKey:     string;
    thirdKey:      string;
    [key: string]: string;
}

Dadurch werden jedoch auch alle Eigenschaftstypen so erzwungen, dass sie mit der Indexsignatur übereinstimmen. Da alle Eigenschaften a sind string, funktioniert es.

Indexsignaturen sind zwar eine leistungsstarke Methode zur Beschreibung des Arrays und des Wörterbuchmusters, sie erzwingen jedoch auch, dass alle Eigenschaften ihrem Rückgabetyp entsprechen.

Bearbeiten:

Wenn die Typen nicht übereinstimmen, kann ein Vereinigungstyp verwendet werden [key: string]: string|IOtherObject;

Bei Union-Typen ist es besser, wenn Sie TypeScript den Typ ableiten lassen, anstatt ihn zu definieren.

// Type of `secondValue` is `string|IOtherObject`
let secondValue = someObject[key];
// Type of `foo` is `string`
let foo = secondValue + '';

Obwohl das etwas chaotisch werden kann, wenn Sie viele verschiedene Typen in den Indexsignaturen haben. Die Alternative dazu ist die Verwendung anyin der Signatur. [key: string]: any;Dann müssten Sie die Typen wie oben besetzen.


Und wenn Ihre Schnittstelle wie die Schnittstelle aussieht ISomeObject {firstKey: string; secondKey: IOtherObject; } das ist wohl nicht möglich?
Jasper Schulte

Vielen Dank! Die Kombination eines beliebigen Typs mit dem Gießen eines Typs pro Fall scheint ein verkaufter Weg zu sein.
Jasper Schulte

Hallo, wie gehe ich mit "anyObject [key: Object] ['name']" um?
Code_Crash

oder sag etwas wie _obj = {}; let _dbKey = _props [key] ['name']; _obj [_dbKey] = this [key]; Hier ist _props Objekt und Objekt [Schlüssel] gibt auch ein Objekt zurück, das die Eigenschaft name hat.
Code_Crash

180

Eine andere Möglichkeit, den Fehler zu vermeiden, besteht darin, die Besetzung wie folgt zu verwenden:

let secondValue: string = (<any>someObject)[key]; (Beachten Sie die Klammern)

Das einzige Problem ist, dass dies nicht mehr typsicher ist, wie Sie es tun any. Sie können jedoch jederzeit zum richtigen Typ zurückkehren.

ps: Ich verwende Typoskript 1.7, bin mir bei früheren Versionen nicht sicher.


20
Um tslint Warnungen zu vermeiden, können Sie auch verwenden:let secondValue: string = (someObject as any)[key];
briosheje

96

TypeScript 2.1 führte eine elegante Methode zur Behandlung dieses Problems ein.

const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];

Wir können während der Kompilierungsphase über das keyofSchlüsselwort auf alle Objekteigenschaftsnamen zugreifen (siehe Änderungsprotokoll ).

Sie müssen nur den stringVariablentyp durch ersetzen keyof ISomeObject. Jetzt weiß der Compiler, dass keyVariablen nur Eigenschaftsnamen von enthalten dürfen ISomeObject.

Vollständiges Beispiel:

interface ISomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   number;
}

const someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   3
};

const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];

// You can mix types in interface, keyof will know which types you refer to.
const keyNumber: (keyof ISomeObject) = 'thirdKey';
const numberValue: number = someObject[keyNumber];

Live-Code auf typescriptlang.org ( noImplicitAnyOption festlegen )

Weiterführende Literatur mit mehr keyofVerwendungen .


6
Es wird jedoch nicht funktionieren, wenn wir keyals const key = (keyof ISomeObject)= 'zweiter' + 'Schlüssel' deklarieren
Enttäuscht

55

Mit der folgenden tsconfig-Einstellung können Sie diese Fehler ignorieren - setzen Sie sie auf true.

unterdrückenImplicitAnyIndexErrors

Unterdrücken Sie noImplicitAny-Fehler beim Indizieren von Objekten ohne Indexsignaturen.


14
Das sollten Sie nicht tun - wahrscheinlich hat jemand in Ihrem Team diese Compiler-Option explizit festgelegt, um den Code kugelsicherer zu machen!
Atsu85

12
Ich bin nicht der Meinung, dass genau dies für diese Option gemacht wurde: Notation in Klammern zulassen mit --noImplicitAny. Passen Sie perfekt zur Frage von op.
Ghetolay

4
Ich stimme @Ghetolay zu. Dies ist auch die einzige Option, wenn eine Änderung der Schnittstelle nicht möglich ist. Zum Beispiel mit internen Schnittstellen wie XMLHttpRequest.
Marco Roy

1
Ich stimme auch @Ghetolay zu. Ich bin gespannt, wie sich dies qualitativ von der Antwort von Pedro Villa Verde unterscheidet (abgesehen von der Tatsache, dass der Code weniger hässlich ist). Wir alle wissen, dass der Zugriff auf eine Objekteigenschaft mithilfe einer Zeichenfolge nach Möglichkeit vermieden werden sollte, aber manchmal genießen wir diese Freiheit, während wir die Risiken verstehen.
Stephen Paul

Es sind nur Kompromisse. Wählen Sie aus, was Ihnen gefällt: weniger Fehleroberfläche und strenger Indexzugriff oder mehr Fehlerfläche und einfacher Zugriff auf unbekannte Indizes. Der TS2.1- keyofOperator kann dabei helfen, alles streng zu halten, siehe Piotrs Antwort!
Trusktr

24

Die oben erwähnte 'keyof'-Lösung funktioniert. Wenn die Variable jedoch nur einmal verwendet wird, z. B. durch ein Objekt usw., können Sie sie auch typisieren.

for (const key in someObject) {
    sampleObject[key] = someObject[key as keyof ISomeObject];
}

Vielen Dank. Dies funktioniert für den Zugriff auf beliebige Schlüssel, wenn die Schlüssel eines anderen Objekts iteriert werden.
Bucabay

19

verwenden keyof typeof

const cat = {
    name: 'tuntun'
}

const key: string = 'name' 

cat[key as keyof typeof cat]

7

Ähnlich wie die Antwort von @Piotr Lewandowski, jedoch innerhalb eines forEach:

const config: MyConfig = { ... };

Object.keys(config)
  .forEach((key: keyof MyConfig) => {
    if (config[key]) {
      // ...
    }
  });

Wie haben Sie das zum Laufen gebracht? Ich versuche das Gleiche (ts 3.8.3), obwohl es einen Fehler auslöst, der besagt : Argument of type '(field: "id" | "url" | "name") => void' is not assignable to parameter of type '(value: string, index: number, array: string[]) => void'. Mein Code sieht so aus Object.keys(components).forEach((comp: Component) => {...}, wo Componentist ein Typ (wie MyConfig).
theGirrafish

6

Deklarieren Sie das Objekt so.

export interface Thread {
    id:number;
    messageIds: number[];
    participants: {
        [key:number]: number
    };
}

6

Kein Indexer? Dann mach dein eigenes!

Ich habe dies global als eine einfache Möglichkeit definiert, eine Objektsignatur zu definieren. Tkann anybei Bedarf sein:

type Indexer<T> = { [ key: string ]: T };

Ich füge nur indexerals Klassenmitglied hinzu.

indexer = this as unknown as Indexer<Fruit>;

Am Ende habe ich folgendes:

constructor(private breakpointResponsiveService: FeatureBoxBreakpointResponsiveService) {

}

apple: Fruit<string>;
pear: Fruit<string>;

// just a reference to 'this' at runtime
indexer = this as unknown as Indexer<Fruit>;

something() {

    this.indexer['apple'] = ...    // typed as Fruit

Dies hat den Vorteil, dass Sie den richtigen Typ zurückerhalten - viele Lösungen, die verwendet <any>werden, verlieren die Eingabe für Sie. Denken Sie daran, dass hierdurch keine Laufzeitüberprüfung durchgeführt wird. Sie müssen immer noch prüfen, ob etwas vorhanden ist, wenn Sie nicht sicher sind, ob es vorhanden ist.

Wenn Sie übermäßig vorsichtig sein möchten und dies verwenden strict, können Sie dies tun, um alle Stellen anzuzeigen, die Sie möglicherweise für eine explizite undefinierte Überprüfung benötigen:

type OptionalIndexed<T> = { [ key: string ]: T | undefined };

Ich finde das normalerweise nicht notwendig, da ich normalerweise weiß, dass es gültig ist, wenn ich als String-Eigenschaft von irgendwoher habe.

Ich fand diese Methode besonders nützlich, wenn ich viel Code habe, der auf den Indexer zugreifen muss, und die Eingabe an nur einer Stelle geändert werden kann.

Hinweis: Ich verwende den strictModus und der unknownist definitiv notwendig.

Der kompilierte Code wird nur so sein indexer = this, dass er dem beim Erstellen von Typoskript _this = thisfür Sie sehr ähnlich ist .


1
In einigen Fällen können Sie möglicherweise Record<T>stattdessen type verwenden. Ich bin derzeit nicht in der Lage, die Details zu untersuchen, aber in einigen begrenzten Fällen funktioniert es möglicherweise besser.
Simon_Weaver

5

Erstellen Sie eine Schnittstelle, um die 'Indexer'-Schnittstelle zu definieren

Erstellen Sie dann Ihr Objekt mit diesem Index.

Hinweis: Dies hat immer noch dieselben Probleme, die andere Antworten in Bezug auf die Durchsetzung des Typs jedes Elements beschrieben haben - aber genau das möchten Sie oft.

Sie können den generischen Typparameter beliebig festlegen: ObjectIndexer< Dog | Cat>

// this should be global somewhere, or you may already be 
// using a library that provides such a type
export interface ObjectIndexer<T> {
  [id: string]: T;
}

interface ISomeObject extends ObjectIndexer<string>
{
    firstKey:   string;
    secondKey:  string;
    thirdKey:   string;
}

let someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue'
};

let key: string = 'secondKey';

let secondValue: string = someObject[key];

Typoskript-Spielplatz


Sie können dies sogar in einer generischen Einschränkung verwenden, wenn Sie einen generischen Typ definieren:

export class SmartFormGroup<T extends IndexableObject<any>> extends FormGroup

Dann kann Tinnerhalb der Klasse indiziert werden :-)


Ich glaube nicht , dass es ein Standard ‚eingebauter‘ Schnittstelle für Dictionarydas darstellt { [key: string]: T }, aber wenn es jemals bitte bearbeite diese Frage ist meine zu entfernen ObjectIndexer.
Simon_Weaver

3

Deklarieren Sie den Typ, dessen Schlüssel Zeichenfolge und Wert sein kann. Deklarieren Sie dann das Objekt mit diesem Typ, und die Flusen werden nicht angezeigt

type MyType = {[key: string]: any};

So wird Ihr Code sein

type ISomeType = {[key: string]: any};

    let someObject: ISomeType = {
        firstKey:   'firstValue',
        secondKey:  'secondValue',
        thirdKey:   'thirdValue'
    };

    let key: string = 'secondKey';

    let secondValue: string = someObject[key];

1

Heute ist es besser, Typen zu deklarieren. Mögen

enum SomeObjectKeys {
    firstKey = 'firstKey',
    secondKey = 'secondKey',
    thirdKey = 'thirdKey',
}

let someObject: Record<SomeObjectKeys, string> = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue',
};

let key: SomeObjectKeys = 'secondKey';

let secondValue: string = someObject[key];

1

Die einfachste Lösung, die ich mit Typescript 3.1 in drei Schritten finden konnte, ist:

1) Schnittstelle erstellen

interface IOriginal {
    original: { [key: string]: any }
}

2) Erstellen Sie eine getippte Kopie

let copy: IOriginal = (original as any)[key];

3) Überall verwenden (JSX enthalten)

<input customProp={copy} />
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.