O elemento tem implicitamente um tipo 'qualquer' porque a expressão do tipo 'string' não pode ser usada para indexar

97

Experimentando o TypeScript para um projeto React e estou preso neste erro:

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

Que aparece quando tento filtrar a matriz em meu componente

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

Até agora, li o artigo "Indexando objetos no TypeScript" ( https://dev.to/kingdaro/indexing-objects-in-typescript-1cgi ), pois havia um erro semelhante, mas tentei adicionar a assinatura do índice a digite plotTypese ainda recebo o mesmo erro.

Meu código de 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;

DLee
fonte

Respostas:

100

Isso acontece porque você tenta acessar a plotOptionspropriedade usando string name. O TypeScript entende que namepode ter qualquer valor, não apenas o nome da propriedade de plotOptions. Portanto, o TypeScript requer a adição de uma assinatura de índice a plotOptions, para que saiba que você pode usar qualquer nome de propriedade em plotOptions. Mas sugiro mudar o tipo de name, então só pode ser uma das plotOptionspropriedades.

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

Agora você poderá usar apenas nomes de propriedades que existem em plotOptions .

Você também deve alterar ligeiramente o seu código.

Primeiro atribua array a alguma variável temporária, para que TS saiba o tipo de 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"
    },
    // ...
}

Em seguida, filtre:

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

Se você está obtendo dados da API e não tem como digitar check props no momento da compilação, a única maneira é adicionar uma assinatura de índice ao seu plotOptions:

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

const plotOptions: tplotOptions = {
    train_1: true,
    train_2: true,
    train_3: true,
    train_4: true
}
Fiodor
fonte
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];

Ruim - o motivo do erro é que o objecttipo é apenas um objeto vazio por padrão. Portanto, não é possível usar um stringtipo para indexar{} .

Melhor - o motivo do erro desaparecer é porque agora estamos dizendo ao compilador que o objargumento será uma coleção de string/anypares string / valor ( ). No entanto, estamos usando oany tipo, então podemos fazer melhor.

Melhor - Testende o objeto vazio. Uestende as chaves de T. Portanto U, sempre existirá T, portanto, pode ser usado como um valor de pesquisa.

Aqui está um exemplo completo:

Troquei a ordem dos genéricos ( U extends keyof Tagora vem antes T extends object) para destacar que a ordem dos genéricos não é importante e você deve selecionar um pedido que faça mais sentido para sua função.

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'

Sintaxe Alternativa

const getKeyValue = <T, K extends keyof T>(obj: T, key: K): T[K] => obj[key];
Alex Mckay
fonte
1
Eu escrevi um pequeno pacote npm com esta função para tornar esta tarefa mais fácil para aqueles que são novos no Typescript.
Alex Mckay
O pacote tem aproximadamente 38 bytes uma vez embalado e minimizado.
Alex Mckay
2
Alex, parece que sua escala do pior ao melhor se aproxima de uma escala do curto e simples até uma prolixa salada de símbolos que só um masoquista poderia amar. Com esse espírito, aqui está uma versão "bester", que é apenas sua versão "melhor" duplicada e embaralhada: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

Eu uso isso:

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

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

Ao usar Object.keys, o seguinte funciona:

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

Quando fazemos algo como este obj [key] Typescript, não podemos saber com certeza se aquela chave existe naquele objeto. O que eu fiz:

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

Sem typescripterro

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

Graças a Alex Mckay, decidi fazer uma configuração dinâmica de adereços:

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

Fiz algumas pequenas alterações na função / uso de Alex McKay que acho que tornam um pouco mais fácil acompanhar por que funciona e também adere à regra de não usar antes de definir .

Primeiro, defina esta função para usar:

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

Da forma como escrevi, o genérico para a função lista o objeto primeiro, depois a propriedade no objeto (podem ocorrer em qualquer ordem, mas se você especificar U extends key of Tantes de T extends objectquebrar a no-use-before-defineregra, também faz sentido ter o objeto primeiro e sua propriedade depois. Finalmente, usei a sintaxe de função mais comum em vez dos operadores de seta (=> ).

De qualquer forma, com essas modificações, você pode apenas usá-lo assim:

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

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

getKeyValue(user, "name")

O que, novamente, acho que é um pouco mais legível.

Nathan Fast
fonte
-11

Isso é o que funcionou para mim. O tsconfig.jsontem uma opção noImplicitAnyque foi definida como true, eu simplesmente defini-o como falsee agora posso acessar propriedades em objetos usando strings.

Zeke
fonte
7
Isso removerá estrito, então não há sentido em usar o texto digitado se continuarmos removendo essas restrições.
Joseph Briggs
Não concordo @JosephBriggs. O texto datilografado traz muitos outros benefícios. A verificação de tipo é um deles. É bom que você possa optar por participar ou não, dependendo dos requisitos do seu projeto.
Zeke
15
Isso não resolve o problema, apenas o ignora.
dias
1
@Zeke eu entendo, companheiro :) Eu estava com pressa para escrever. O que eu quis dizer é que, se continuarmos resolvendo os problemas apenas dizendo para ignorar, então não há sentido em primeiro lugar. mas, novamente, tudo depende do projeto e das decisões por projeto.
Joseph Briggs
Eu prefiro isso ... mesmo linguagem de tipo forte como c # tem "var". Consultando stackoverflow.com/questions/62377614/… ... Um pouco mais de engenharia para colocar todo tipo de código bumping para acessar uma propriedade simples.
Eric