Das Element hat implizit den Typ 'any', da der Ausdruck vom Typ 'string' nicht zum Indizieren verwendet werden kann

86

Ich probiere TypeScript für ein React-Projekt aus und bin bei diesem Fehler festgefahren:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ train_1: boolean; train_2: boolean; train_3: boolean; train_4: boolean; }'.
  No index signature with a parameter of type 'string' was found on type '{ train_1: boolean; train_2: boolean; train_3: boolean; train_4: boolean; }'

Dies wird angezeigt, wenn ich versuche, das Array in meiner Komponente zu filtern

.filter(({ name }) => plotOptions[name]);

Bisher habe ich mir den Artikel "Indizieren von Objekten in TypeScript" ( https://dev.to/kingdaro/indexing-objects-in-typescript-1cgi ) angesehen, da er einen ähnlichen Fehler aufwies, aber ich habe versucht, die Indexsignatur hinzuzufügen Typ plotTypesund ich bekomme immer noch den gleichen Fehler.

Mein Komponentencode:

import React, { Component } from "react";
import createPlotlyComponent from "react-plotly.js/factory";
import Plotly from "plotly.js-basic-dist";
const Plot = createPlotlyComponent(Plotly);

interface IProps {
  data: any;
}

interface IState {
  [key: string]: plotTypes;
  plotOptions: plotTypes;
}

type plotTypes = {
  [key: string]: boolean;
  train_1: boolean;
  train_2: boolean;
  train_3: boolean;
  train_4: boolean;
};

interface trainInfo {
  name: string;
  x: Array<number>;
  y: Array<number>;
  type: string;
  mode: string;
}

class FiltrationPlots extends Component<IProps, IState> {
  readonly state = {
    plotOptions: {
      train_1: true,
      train_2: true,
      train_3: true,
      train_4: true
    }
  };
  render() {
    const { data } = this.props;
    const { plotOptions } = this.state;

    if (data.filtrationData) {
      const plotData: Array<trainInfo> = [
        {
          name: "train_1",
          x: data.filtrationData.map((i: any) => i["1-CumVol"]),
          y: data.filtrationData.map((i: any) => i["1-PressureA"]),
          type: "scatter",
          mode: "lines"
        },
        {
          name: "train_2",
          x: data.filtrationData.map((i: any) => i["2-CumVol"]),
          y: data.filtrationData.map((i: any) => i["2-PressureA"]),
          type: "scatter",
          mode: "lines"
        },
        {
          name: "train_3",
          x: data.filtrationData.map((i: any) => i["3-CumVol"]),
          y: data.filtrationData.map((i: any) => i["3-PressureA"]),
          type: "scatter",
          mode: "lines"
        },
        {
          name: "train_4",
          x: data.filtrationData.map((i: any) => i["4-CumVol"]),
          y: data.filtrationData.map((i: any) => i["4-PressureA"]),
          type: "scatter",
          mode: "lines"
        }
      ].filter(({ name }) => plotOptions[name]);
      return (
        <Plot
          data={plotData}
          layout={{ width: 1000, height: 1000, title: "A Fancy Plot" }}
        />
      );
    } else {
      return <h1>No Data Loaded</h1>;
    }
  }
}

export default FiltrationPlots;

DLee
quelle

Antworten:

84

Dies geschieht, weil Sie versuchen, plotOptionsmit einer Zeichenfolge auf die Eigenschaft zuzugreifen name. TypeScript versteht, dass dies nameeinen beliebigen Wert haben kann, nicht nur den Eigenschaftsnamen von plotOptions. TypeScript muss also eine Indexsignatur hinzufügen plotOptions, damit Sie wissen, dass Sie einen beliebigen Eigenschaftsnamen in verwenden können plotOptions. Aber ich schlage vor, den Typ zu ändern name, damit es nur eine der plotOptionsEigenschaften sein kann.

interface trainInfo {
    name: keyof typeof plotOptions;
    x: Array<number>;
    y: Array<number>;
    type: string;
    mode: string;
}

Jetzt können Sie nur Eigenschaftsnamen verwenden, die in vorhanden sind plotOptions.

Sie müssen auch Ihren Code leicht ändern.

Weisen Sie zuerst einer temporären Variablen ein Array zu, damit TS den Array-Typ kennt:

const plotDataTemp: Array<trainInfo> = [
    {
      name: "train_1",
      x: data.filtrationData.map((i: any) => i["1-CumVol"]),
      y: data.filtrationData.map((i: any) => i["1-PressureA"]),
      type: "scatter",
      mode: "lines"
    },
    // ...
}

Dann filtern:

const plotData = plotDataTemp.filter(({ name }) => plotOptions[name]);

Wenn Sie Daten von der API erhalten und beim Kompilieren keine Möglichkeit haben, Prüfstützen einzugeben, können Sie nur Folgendes hinzufügen plotOptions:

type tplotOptions = {
    [key: string]: boolean
}

const plotOptions: tplotOptions = {
    train_1: true,
    train_2: true,
    train_3: true,
    train_4: true
}
Fjodor
quelle
29
// bad
const _getKeyValue = (key: string) => (obj: object) => obj[key];

// better
const _getKeyValue_ = (key: string) => (obj: Record<string, any>) => obj[key];

// best
const getKeyValue = <T extends object, U extends keyof T>(key: U) => (obj: T) =>
  obj[key];

Schlecht - Der Grund für den Fehler ist, dass der objectTyp standardmäßig nur ein leeres Objekt ist. Daher ist es nicht möglich, einen stringTyp zum Indizieren zu verwenden {}.

Besser - der Grund, warum der Fehler verschwindet, ist, dass wir dem Compiler jetzt mitteilen, dass das objArgument eine Sammlung von Zeichenfolgen / Wert ( string/any) -Paaren sein wird. Wir verwenden jedoch den anyTyp, damit wir es besser machen können.

Am besten - Terweitert leeres Objekt. Uerweitert die Tasten von T. Daher Uwird immer auf vorhanden sein T, daher kann es als Nachschlagewert verwendet werden.

Hier ist ein vollständiges Beispiel:

Ich habe die Reihenfolge der Generika geändert ( U extends keyof Tkommt jetzt vorher T extends object), um hervorzuheben, dass die Reihenfolge der Generika nicht wichtig ist und Sie eine Reihenfolge auswählen sollten, die für Ihre Funktion am sinnvollsten ist.

const getKeyValue = <U extends keyof T, T extends object>(key: U) => (obj: T) =>
  obj[key];

interface User {
  name: string;
  age: number;
}

const user: User = {
  name: "John Smith",
  age: 20
};

const getUserName = getKeyValue<keyof User, User>("name")(user);

// => 'John Smith'

Alternative Syntax

const getKeyValue = <T, K extends keyof T>(obj: T, key: K): T[K] => obj[key];
Alex Mckay
quelle
1
Ich habe ein kleines npm-Paket mit dieser Funktion geschrieben, um diese Aufgabe für diejenigen zu vereinfachen, die neu in Typescript sind.
Alex Mckay
Das Paket besteht aus ungefähr 38 Bytes, sobald es gepackt und minimiert wurde.
Alex Mckay
5

Ich benutze das:

interface IObjectKeys {
  [key: string]: string;
}

interface IDevice extends IObjectKeys {
  id: number;
  room_id: number;
  name: string;
  type: string;
  description: string;
}
Gennady Magomaev
quelle
4

Wenn wir so etwas tun, kann obj [key] Typescript nicht sicher wissen, ob dieser Schlüssel in diesem Objekt vorhanden ist. Was ich getan habe:

Object.entries(data).forEach(item => {
    formData.append(item[0], item[1]);
});
Alonad
quelle
2

Bei der Verwendung Object.keysfunktioniert Folgendes:

Object.keys(this)
    .forEach(key => {
      console.log(this[key as keyof MyClass]);
    });
Noel Yap
quelle
0

Ohne typescriptFehler

    const formData = new FormData();
    Object.keys(newCategory).map((k,i)=>{  
        var d =Object.values(newCategory)[i];
        formData.append(k,d) 
    })
Moumit
quelle
0

Dank Alex Mckay hatte ich den Entschluss, Requisiten dynamisch einzustellen:

  for(let prop in filter)
      (state.filter as Record<string, any>)[prop] = filter[prop];
Andrew Zagarichuk
quelle
0

Ich habe einige kleine Änderungen an Alex McKays Funktion / Verwendung vorgenommen, die es meiner Meinung nach ein wenig einfacher machen, zu verstehen, warum es funktioniert, und die auch die Regel " Nicht verwenden vor dem Definieren" einhalten.

Definieren Sie zunächst diese zu verwendende Funktion:

const getKeyValue = function<T extends object, U extends keyof T> (obj: T, key: U) { return obj[key] }

So wie ich es geschrieben habe, listet das Generikum für die Funktion zuerst das Objekt und dann die Eigenschaft des Objekts auf (diese können in beliebiger Reihenfolge auftreten, aber wenn Sie angeben, U extends key of Tbevor T extends objectSie gegen die no-use-before-defineRegel verstoßen, ist dies auch nur sinnvoll Um das Objekt an erster Stelle und seine Eigenschaft an zweiter Stelle zu haben. Schließlich habe ich anstelle der Pfeiloperatoren ( =>) die häufigere Funktionssyntax verwendet .

Wie auch immer, mit diesen Modifikationen können Sie es einfach so verwenden:

interface User {
  name: string;
  age: number;
}

const user: User = {
  name: "John Smith",
  age: 20
};

getKeyValue(user, "name")

Was ich wiederum etwas lesbarer finde.

Nathan Fast
quelle
-9

Das hat bei mir funktioniert. Das tsconfig.jsonhat eine Option noImplicitAny, auf die es gesetzt wurde true, ich setze es einfach auf falseund jetzt kann ich mit Strings auf Eigenschaften in Objekten zugreifen.

Zeke
quelle
6
Dadurch wird strikt entfernt, und es macht keinen Sinn, Typoskript zu verwenden, wenn wir diese Einschränkungen weiterhin entfernen.
Joseph Briggs
Ich stimme @JosephBriggs nicht zu. Typoskript bringt so viele andere Vorteile auf den Tisch. Typprüfung ist eine davon. Schön, dass Sie sich je nach den Anforderungen Ihres Projekts an- oder abmelden können.
Zeke
13
Dies löst das Problem nicht, es ignoriert es einfach.
Tag dreht sich
1
@Zeke Ich verstehe Kumpel :) Ich war in Eile zu schreiben. Was ich damit gemeint habe ist, dass es überhaupt keinen Sinn macht, wenn wir die Probleme weiter lösen, indem wir sie nur zum Ignorieren auffordern. Andererseits hängt alles vom Projekt und den Entscheidungen pro Projekt ab.
Joseph Briggs
Ich bevorzuge dies ... sogar starke Typensprachen wie c # haben "var". Verweis stackoverflow.com/questions/62377614/... ... Bit - A über engineered alle Art von setzen Bumping - Code eine einfache Eigenschaft zu erreichen.
Eric