Елемент неявно має тип "будь-який", оскільки вираз типу "рядок" не може використовуватися для індексації


85

Випробовуючи 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;

Відповіді:


82

Це відбувається тому, що ви намагаєтесь отримати доступ до 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
}

28
// 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пар string / value ( ). Однак ми використовуємо 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

4

Коли ми робимо щось на зразок цього obj [key] Typescript не може точно знати, чи існує цей ключ у цьому об’єкті. Що я зробив:

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

4

Я використовую це:

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

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

2

Під час використання Object.keysпрацює наступне:

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

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 Маккея функції / використання , що я думаю , що зробити це трохи легше слідувати , чому це працює , а також дотримується НЕ споживчої до-визначення правила.

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

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")

Що, знову ж таки, я вважаю трохи читабельнішим.


-9

Це те, що мені вдалося. У tsconfig.jsonпараметра є параметр, для noImplicitAnyякого він був встановлений true, я просто встановив його, falseі тепер я можу отримати доступ до властивостей об’єктів за допомогою рядків.


6
Це видалить суворе, тоді немає сенсу використовувати машинопис, якщо ми продовжуємо видаляти ці обмеження.
Джозеф Бріггс,

Я не згоден @JosephBriggs. Машинопис приносить до таблиці так багато інших переваг. Перевірка типу - одна з них. Приємно, що ви можете прийняти або відмовитись залежно від вимог вашого проекту.
Зеке

13
Це не вирішує проблему, а просто ігнорує її.
thedayturns

1
@Zeke Я розумію, товариш :) Я дуже поспішно писав. Я мав на увазі, що якщо ми продовжуємо вирішувати проблеми, просто кажучи йому ігнорувати, то в першу чергу це не має сенсу. але знову ж таки все залежить від проекту та рішень щодо проекту.
Джозеф Бриггс,

Я віддаю перевагу цій ... навіть сильній мові типу, як c #, має "var". Посилаючись на stackoverflow.com/questions/62377614/… ... Трохи надмірно спроектовано, щоб покласти всілякі коди зіткнення для доступу до простої властивості.
Ерік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.