Element niejawnie ma typ „any”, ponieważ wyrażenie typu „string” nie może być używane do indeksowania

86

Wypróbowuję TypeScript dla projektu React i utknąłem na tym błędzie:

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; }'

Który pojawia się, gdy próbuję przefiltrować tablicę w moim komponencie

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

Do tej pory przyjrzałem się artykułowi „Indeksowanie obiektów w TypeScript” ( https://dev.to/kingdaro/indexing-objects-in-typescript-1cgi ), ponieważ zawierał on podobny błąd, ale próbowałem dodać podpis indeksu do wpisz plotTypesi nadal otrzymuję ten sam błąd.

Mój kod komponentu:

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
źródło

Odpowiedzi:

84

Dzieje się tak, ponieważ próbujesz uzyskać dostęp do plotOptionswłaściwości za pomocą ciągu znaków name. TypeScript rozumie, że namemoże mieć dowolną wartość, nie tylko nazwę właściwości z plotOptions. Dlatego TypeScript wymaga dodania podpisu indeksu do plotOptions, aby wiedział, że możesz użyć dowolnej nazwy właściwości w plotOptions. Ale proponuję zmienić typ name, więc może to być tylko jedna z plotOptionswłaściwości.

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

Teraz będziesz mógł używać tylko nazw właściwości, które istnieją w plotOptions.

Musisz także nieco zmienić swój kod.

Najpierw przypisz tablicę do jakiejś zmiennej tymczasowej, aby TS znał typ tablicy:

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"
    },
    // ...
}

Następnie filtruj:

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

Jeśli pobierasz dane z API i nie masz możliwości wpisania check props w czasie kompilacji, jedynym sposobem jest dodanie podpisu indeksu do plotOptions:

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

const plotOptions: tplotOptions = {
    train_1: true,
    train_2: true,
    train_3: true,
    train_4: true
}
Fiodor
źródło
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];

Źle - przyczyną błędu jest objectdomyślnie po prostu pusty obiekt. Dlatego nie można używać stringtypu do indeksowania {}.

Lepiej - powodem zniknięcia błędu jest to, że teraz mówimy kompilatorowi, że objargument będzie zbiorem string/anypar łańcuch / wartość ( ). Jednak używamy tego anytypu, więc możemy zrobić lepiej.

Najlepsza - Trozszerza pusty obiekt. Urozszerza klucze T. Dlatego Uzawsze będzie istnieć T, dlatego może być używany jako wartość wyszukiwania.

Oto pełny przykład:

Zmieniłem kolejność typów ogólnych ( U extends keyof Tteraz jest to wcześniej T extends object), aby podkreślić, że kolejność typów ogólnych nie jest ważna i należy wybrać kolejność, która jest najbardziej odpowiednia dla Twojej funkcji.

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'

Alternatywna składnia

const getKeyValue = <T, K extends keyof T>(obj: T, key: K): T[K] => obj[key];
Alex Mckay
źródło
1
Napisałem mały pakiet npm z tą funkcją, aby ułatwić to zadanie dla tych, którzy są nowicjuszami w Typescript.
Alex Mckay
Po spakowaniu i zminimalizowaniu pakiet ma około 38 bajtów.
Alex Mckay
Alex, wygląda na to, że twoja skala od najgorszej do najlepszej jest zbliżona do skali od krótkiej i prostej aż po sałatkę z symbolem prolix, którą mógłby pokochać tylko masochista. W tym duchu, oto "najlepsza" wersja, która jest po prostu twoją "najlepszą" wersją powieloną i przetasowaną:d ee((oent V[jkkncnt=kfay u skk==e>Ugs(< :ds b]uUeT eKne e=x>:dTy: lc,n=b;T=by>o[x oo<U yc ,t Ketgt )c)yo oe eTV aej ; Txb )t> se de esytkjeeUe otj]b j o:oebe)ytlT(eejfs>toyn> x
webb
5

Używam tego:

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

interface IDevice extends IObjectKeys {
  id: number;
  room_id: number;
  name: string;
  type: string;
  description: string;
}
Gennady Magomaev
źródło
4

Kiedy robimy coś takiego obj [klucz], Typescript nie może być pewien, czy ten klucz istnieje w tym obiekcie. Co ja zrobiłem:

Object.entries(data).forEach(item => {
    formData.append(item[0], item[1]);
});
Alonad
źródło
2

Podczas używania Object.keysdziała:

Object.keys(this)
    .forEach(key => {
      console.log(this[key as keyof MyClass]);
    });
Noel Yap
źródło
0

Bez typescriptbłędu

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

Dzięki Alexowi Mckayowi postanowiłem dynamicznie ustawić rekwizyty:

  for(let prop in filter)
      (state.filter as Record<string, any>)[prop] = filter[prop];
Andrew Zagarichuk
źródło
0

Wprowadziłem kilka drobnych zmian w funkcji / użytkowaniu Alexa McKaya, które moim zdaniem ułatwiają śledzenie, dlaczego to działa, a także przestrzega zasady nieużywania przed zdefiniowaniem .

Najpierw zdefiniuj tę funkcję do użycia:

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

W sposób, w jaki to napisałem, generyczna funkcja wyświetla najpierw obiekt, a następnie właściwość obiektu (mogą one wystąpić w dowolnej kolejności, ale jeśli określisz U extends key of Tprzed T extends objectzłamaniem no-use-before-definereguły, to po prostu ma sens aby obiekt był pierwszy, a jego właściwość druga. Wreszcie, użyłem bardziej powszechnej składni funkcji zamiast operatorów strzałek ( =>).

W każdym razie, z tymi modyfikacjami możesz go po prostu użyć w ten sposób:

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

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

getKeyValue(user, "name")

Co, znowu, uważam za nieco bardziej czytelne.

Nathan Fast
źródło
-9

To właśnie zadziałało dla mnie. tsconfig.jsonPosiada opcję noImplicitAny, że został ustawiony true, ja po prostu ustawić go falsei teraz mogę uzyskać dostęp do właściwości w obiektach wykorzystujących sznurki.

Zeke
źródło
6
Spowoduje to usunięcie ograniczeń, więc nie ma sensu używać maszynopisu, jeśli będziemy nadal usuwać te ograniczenia.
Joseph Briggs
Nie zgadzam się @JosephBriggs. Maszynopis niesie ze sobą wiele innych korzyści. Sprawdzanie typu jest jednym z nich. Fajnie, że możesz wyrazić zgodę lub zrezygnować w zależności od wymagań projektu.
Zeke
13
To nie rozwiązuje problemu, po prostu go ignoruje.
thedayturns
1
@Zeke Rozumiem kolego :) Spieszyłem się z pisaniem. Chodziło mi o to, że jeśli będziemy nadal rozwiązywać problemy, po prostu mówiąc mu, aby je ignorował, to po pierwsze nie ma to sensu. ale potem znowu wszystko zależy od projektu i decyzji dotyczących każdego projektu.
Joseph Briggs,
Wolę ten ... nawet silny język typu, taki jak c #, ma "var". Odnosząc się do stackoverflow.com/questions/62377614/ ... Nieco zbyt rozbudowane, aby umieszczać wszelkiego rodzaju kody wpadające, aby uzyskać dostęp do prostej właściwości.
Eric