Элемент неявно имеет тип any, поскольку выражение типа string не может использоваться для индексации

97

Пробую TypeScript для проекта React, и я застрял на этой ошибке:

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

Что появляется, когда я пытаюсь отфильтровать массив в моем компоненте

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

До сих пор я смотрел статью «Индексирование объектов в TypeScript» ( https://dev.to/kingdaro/indexing-objects-in-typescript-1cgi ), поскольку в ней была аналогичная ошибка, но я попытался добавить подпись индекса в типа, plotTypesи я все еще получаю ту же ошибку.

Код моего компонента:

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
источник

Ответы:

100

Это происходит потому, что вы пытаетесь получить доступ к plotOptionsсвойству с помощью строки name. TypeScript понимает, что nameможет иметь любое значение, а не только имя свойства plotOptions. Таким образом, TypeScript требует добавления подписи индекса plotOptions, чтобы он знал, что вы можете использовать любое имя свойства в plotOptions. Но я предлагаю изменить тип name, так что это может быть только одно из plotOptionsсвойств.

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

Теперь вы сможете использовать только те имена свойств, которые существуют в plotOptions.

Вам также придется немного изменить свой код.

Сначала присвойте массив некоторой временной переменной, чтобы TS знал тип массива:

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

Затем отфильтруйте:

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

Если вы получаете данные из API и у вас нет возможности ввести проверочные реквизиты во время компиляции, единственный способ - добавить подпись индекса к вашему 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];

Плохо - причина ошибки в objectтом, что по умолчанию тип просто пустой объект. Поэтому нельзя использовать stringтип для индексации {}.

Лучше - причина, по которой ошибка исчезает, заключается в том, что теперь мы сообщаем компилятору, что objаргумент будет набором string/anyпар строка / значение ( ). Однако мы используем anyтип, поэтому можем добиться большего.

Лучшее - Tрасширяет пустой объект. Uрасширяет ключи T. Поэтому Uвсегда будет существовать T, поэтому его можно использовать в качестве значения поиска.

Вот полный пример:

Я поменял порядок дженериков ( U extends keyof Tтеперь будет раньше T extends object), чтобы подчеркнуть, что порядок дженериков не важен, и вы должны выбрать порядок, который имеет наибольшее значение для вашей функции.

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'

Альтернативный синтаксис

const getKeyValue = <T, K extends keyof T>(obj: T, key: K): T[K] => obj[key];
Алекс Маккей
источник
1
Я написал крошечный пакет npm с этой функцией, чтобы облегчить эту задачу для новичков в Typescript.
Alex Mckay
После упаковки и минификации размер пакета составляет примерно 38 байт.
Alex Mckay
2
Алекс, похоже, ваша шкала от худшего к лучшему приближается к шкале от короткого и простого до многословного символа-салата, который мог бы полюбить только мазохист. В этом духе, вот "бестерская" версия, которая является вашей "лучшей" версией, дублированной и перемешанной: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

Я использую это:

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

interface IDevice extends IObjectKeys {
  id: number;
  room_id: number;
  name: string;
  type: string;
  description: string;
}
Геннадий Магомаев
источник
8

При использовании Object.keysработает следующее:

Object.keys(this)
    .forEach(key => {
      console.log(this[key as keyof MyClass]);
    });
Ноэль Яп
источник
4

Когда мы делаем что-то вроде этого obj [key], Typescript не может точно знать, существует ли этот ключ в этом объекте. Что я сделал:

Object.entries(data).forEach(item => {
    formData.append(item[0], item[1]);
});
Алонад
источник
0

Без typescriptошибки

    const formData = new FormData();
    Object.keys(newCategory).map((k,i)=>{  
        var d =Object.values(newCategory)[i];
        formData.append(k,d) 
    })
Мумит
источник
0

Благодаря Алексу Маккею я решил динамически установить реквизит:

  for(let prop in filter)
      (state.filter as Record<string, any>)[prop] = filter[prop];
Андрей Загаричук
источник
0

Я внес некоторые небольшие изменения в функцию / использование Alex McKay, которые, как мне кажется, упрощают понимание того, почему она работает, а также придерживаются правила « no-use-before-define» .

Сначала определите эту функцию для использования:

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

В том, как я это написал, общий для функции сначала перечисляет объект, а затем свойство объекта (они могут происходить в любом порядке, но если вы укажете U extends key of Tдо T extends objectтого, как нарушите no-use-before-defineправило, это также имеет смысл чтобы объект был первым, а его свойство - вторым. Наконец, я использовал более общий синтаксис функции вместо операторов стрелки ( =>).

В любом случае, с этими модификациями вы можете просто использовать его так:

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

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

getKeyValue(user, "name")

Что, опять же, мне кажется более читаемым.

Натан Фаст
источник
-11

Это то, что у меня сработало. У tsconfig.jsonнего есть параметр, noImplicitAnyкоторый был установлен true, я просто установил его, falseи теперь я могу получить доступ к свойствам в объектах с помощью строк.

Зик
источник
7
Это уберет строгое, тогда нет смысла использовать машинописный текст, если мы продолжаем снимать эти ограничения.
Джозеф Бриггс,
Я не согласен с @JosephBriggs. Машинопись дает много других преимуществ. Проверка типа - одна из них. Приятно, что вы можете зарегистрироваться или отказаться в зависимости от требований вашего проекта.
Зик
15
Это не решает проблемы, просто игнорирует ее.
thedayturns
1
@Zeke Я понимаю, дружище :) Я торопился писать. Я имел в виду, что если мы продолжаем решать проблемы, просто приказывая ему игнорировать, то в этом нет никакого смысла. но опять же, все зависит от проекта и решений для каждого проекта.
Джозеф Бриггс,
Я предпочитаю это ... даже язык с сильным типом, такой как C #, имеет "var". Ссылаясь на stackoverflow.com/questions/62377614/… ... Немного перестроен, чтобы поместить всевозможный код для доступа к простому свойству.
Эрик