El elemento tiene implícitamente un tipo 'cualquier' porque la expresión del tipo 'cadena' no se puede usar para indexar

85

Probando TypeScript para un proyecto de React y estoy atascado en este error:

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 cuando intento filtrar la matriz en mi componente

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

Hasta ahora miré el artículo "Indexación de objetos en TypeScript" ( https://dev.to/kingdaro/indexing-objects-in-typescript-1cgi ) ya que tenía un error similar, pero traté de agregar la firma del índice a escriba plotTypesy sigo recibiendo el mismo error.

Mi 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
fuente

Respuestas:

83

Esto sucede porque intentas acceder a la plotOptionspropiedad usando una cadena name. TypeScript entiende que namepuede tener cualquier valor, no solo el nombre de la propiedad plotOptions. Por lo tanto, TypeScript requiere agregar una firma de índice plotOptions, para que sepa que puede usar cualquier nombre de propiedad en plotOptions. Pero sugiero cambiar el tipo de name, por lo que solo puede ser una de las plotOptionspropiedades.

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

Ahora podrá usar solo los nombres de propiedad que existen en plotOptions.

También tienes que cambiar ligeramente tu código.

Primero asigne una matriz a alguna variable temporal, para que TS sepa el tipo de matriz:

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

Luego filtrar:

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

Si obtiene datos de la API y no tiene forma de escribir accesorios de verificación en el momento de la compilación, la única forma es agregar la firma de índice a su plotOptions:

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

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

Malo: el motivo del error es que el objecttipo es solo un objeto vacío de forma predeterminada. Por lo tanto, no es posible utilizar un stringtipo para indexar {}.

Mejor: la razón por la que el error desaparece es porque ahora le estamos diciendo al compilador que el objargumento será una colección de string/anypares cadena / valor ( ). Sin embargo, estamos usando el anytipo, por lo que podemos hacerlo mejor.

Mejor: Textiende el objeto vacío. Uextiende las claves de T. Por Ulo tanto , siempre existirá T, por lo tanto, se puede usar como valor de búsqueda.

Aquí hay un ejemplo completo:

Cambié el orden de los genéricos ( U extends keyof Tahora viene antes T extends object) para resaltar que el orden de los genéricos no es importante y debe seleccionar un orden que tenga más sentido para su función.

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'

Sintaxis alternativa

const getKeyValue = <T, K extends keyof T>(obj: T, key: K): T[K] => obj[key];
Alex Mckay
fuente
1
Escribí un pequeño paquete npm con esta función para facilitar esta tarea a aquellos que son nuevos en Typecript.
Alex Mckay
El paquete tiene aproximadamente 38 bytes una vez empaquetado y minimizado.
Alex Mckay
4

Cuando hacemos algo como esto obj [clave] Typescript no podemos saber con certeza si esa clave existe en ese objeto. Lo que hice:

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

Yo uso esto:

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

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

Cuando se usa Object.keys, funciona lo siguiente:

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

Sin typescripterror

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

Gracias a Alex Mckay, tuve la determinación de configurar dinámicamente los accesorios:

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

Hice algunos pequeños cambios en la función / uso de Alex McKay que creo que hacen que sea un poco más fácil seguir por qué funciona y también se adhiere a la regla de no usar antes de definir .

Primero, defina esta función para usar:

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

En la forma en que lo he escrito, el genérico de la función enumera el objeto primero, luego la propiedad del objeto en segundo lugar (estos pueden ocurrir en cualquier orden, pero si especifica U extends key of Tantes de T extends objectromper la no-use-before-defineregla, y también tiene sentido tener el objeto primero y su propiedad en segundo lugar. Finalmente, he usado la sintaxis de función más común en lugar de los operadores de flecha ( =>).

De todos modos, con esas modificaciones puedes usarlo así:

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

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

getKeyValue(user, "name")

Lo cual, de nuevo, me parece un poco más legible.

Nathan Fast
fuente
-9

Esto es lo que funcionó para mí. El tsconfig.jsontiene una opción noImplicitAnyque se establece en true, yo simplemente puse a falsey ahora puede acceder a las propiedades de los objetos utilizando cuerdas.

Zeke
fuente
6
Esto eliminará estricto, entonces no tiene sentido usar mecanografiado si seguimos eliminando estas restricciones.
Joseph Briggs
No estoy de acuerdo @JosephBriggs. Typecript trae a la mesa muchos otros beneficios. La verificación de tipo es uno de ellos. Es bueno que pueda optar por participar o no según los requisitos de su proyecto.
Zeke
13
Esto no resuelve el problema, simplemente lo ignora.
gira el
1
@Zeke, lo entiendo amigo :) Tenía prisa escribiendo. Lo que quise decir es que si seguimos resolviendo los problemas simplemente diciéndole que lo ignore, entonces no tiene sentido en primer lugar. pero, de nuevo, todo depende del proyecto y de las decisiones por proyecto.
Joseph Briggs
Prefiero esto ... incluso los lenguajes de tipo fuerte como c # tienen "var". Refiriéndose a stackoverflow.com/questions/62377614/… ... Un poco más de ingeniería para poner todo tipo de código de golpe para acceder a una propiedad simple.
Eric