L'elemento ha implicitamente un tipo "any" perché l'espressione di tipo "string" non può essere utilizzata per l'indicizzazione


98

Provando TypeScript per un progetto React e sono bloccato su questo errore:

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

Che appare quando provo a filtrare l'array nel mio componente

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

Finora ho guardato l'articolo "Indicizzazione di oggetti in TypeScript" ( https://dev.to/kingdaro/indexing-objects-in-typescript-1cgi ) poiché aveva un errore simile, ma ho provato ad aggiungere la firma dell'indice a digita plotTypese ricevo ancora lo stesso errore.

Il mio codice componente:

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;

Risposte:


101

Ciò accade perché si tenta di accedere alla plotOptionsproprietà utilizzando la stringa name. TypeScript comprende che namepuò avere qualsiasi valore, non solo il nome della proprietà da plotOptions. Quindi TypeScript richiede di aggiungere la firma dell'indice plotOptions, quindi sa che puoi usare qualsiasi nome di proprietà in plotOptions. Ma suggerisco di cambiare tipo di name, quindi può essere solo una delle plotOptionsproprietà.

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

Ora sarai in grado di utilizzare solo nomi di proprietà che esistono in plotOptions.

Devi anche cambiare leggermente il tuo codice.

Per prima cosa assegna l'array a qualche variabile temporanea, in modo che TS conosca il tipo di array:

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

Quindi filtra:

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

Se stai ricevendo dati dall'API e non hai modo di digitare check props in fase di compilazione, l'unico modo è aggiungere la firma dell'indice al tuo plotOptions:

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

const plotOptions: tplotOptions = {
    train_1: true,
    train_2: true,
    train_3: true,
    train_4: true
}

34
// 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];

Cattivo: il motivo dell'errore è che il objecttipo è solo un oggetto vuoto per impostazione predefinita. Pertanto non è possibile utilizzare un stringtipo da indicizzare {}.

Meglio: il motivo per cui l'errore scompare è perché ora stiamo dicendo al compilatore che l' objargomento sarà una raccolta di string/anycoppie stringa / valore ( ). Tuttavia, stiamo usando il anytipo, quindi possiamo fare di meglio.

Migliore: Testende l'oggetto vuoto. Uestende le chiavi di T. Pertanto Uesisterà sempre su T, quindi può essere utilizzato come valore di ricerca.

Ecco un esempio completo:

Ho cambiato l'ordine dei generici ( U extends keyof Tora viene prima T extends object) per evidenziare che l'ordine dei generici non è importante e dovresti selezionare un ordine che abbia più senso per la tua funzione.

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'

Sintassi alternativa

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

1
Ho scritto un minuscolo pacchetto npm con questa funzione per rendere questo compito più facile per coloro che sono nuovi a Typescript.
Alex Mckay

Il pacchetto è di circa 38 byte una volta confezionato e minimizzato.
Alex Mckay

2
Alex, sembra che la tua scala dal peggiore al migliore si avvicini a una scala da breve e semplice fino a un'insalata di simboli prolissi che solo un masochista potrebbe amare. In questo spirito, ecco una versione "migliore", che è solo la tua versione "migliore" duplicata e mescolata: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

10

Io uso questo:

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

interface IDevice extends IObjectKeys {
  id: number;
  room_id: number;
  name: string;
  type: string;
  description: string;
}

8

Quando si utilizza Object.keys, funziona quanto segue:

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

4

Quando facciamo qualcosa del genere obj [chiave] Typescript non può sapere con certezza se quella chiave esiste in quell'oggetto. Cosa ho fatto:

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

0

Senza typescripterrori

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

0

Grazie ad Alex Mckay ho deciso di impostare dinamicamente un oggetto di scena:

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

0

Ho apportato alcune piccole modifiche alla funzione / utilizzo di Alex McKay che penso rendano un po 'più facile seguire il motivo per cui funziona e aderisce anche alla regola del non utilizzo prima della definizione .

Innanzitutto, definisci questa funzione da utilizzare:

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

Nel modo in cui l'ho scritto, il generico per la funzione elenca prima l'oggetto, poi la proprietà sull'oggetto per secondo (questi possono verificarsi in qualsiasi ordine, ma se specifichi U extends key of Tprima T extends objectdi infrangere la no-use-before-defineregola, e ha anche senso per avere prima l'oggetto e poi la sua proprietà. Infine, ho usato la sintassi delle funzioni più comune invece degli operatori freccia ( =>).

Ad ogni modo, con queste modifiche puoi semplicemente usarlo in questo modo:

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

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

getKeyValue(user, "name")

Il che, ancora una volta, trovo un po 'più leggibile.


-11

Questo è quello che ha funzionato per me. L' tsconfig.jsonha un'opzione noImplicitAnyche è stato impostato true, ho semplicemente impostato a falsee ora posso accedere alle proprietà di oggetti utilizzando le stringhe.


7
Questo rimuoverà rigoroso, quindi non ha senso usare il dattiloscritto se continuiamo a rimuovere queste restrizioni.
Joseph Briggs

Non sono d'accordo @JosephBriggs. Il dattiloscritto porta in tavola tanti altri vantaggi. Il controllo del tipo è uno di questi. È bello che tu possa accettare o rifiutare a seconda dei requisiti del tuo progetto.
Zeke

15
Questo non risolve il problema, lo ignora.
gira il

1
@Zeke capisco amico :) Ero di fretta a scrivere. Quello che volevo dire è che se continuiamo a risolvere i problemi semplicemente dicendogli di ignorarli, non ha senso in primo luogo. ma poi di nuovo tutto dipende dal progetto e dalle decisioni per progetto.
Joseph Briggs

Preferisco questo ... anche un linguaggio di tipo forte come c # ha "var". Riferendosi stackoverflow.com/questions/62377614/... ... un po 'più progettato per mettere tutti i tipi di urti codice per accedere a una proprietà semplice.
Eric,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.