Phần tử ngầm định có kiểu 'bất kỳ' vì không thể sử dụng biểu thức của kiểu 'chuỗi' để lập chỉ mục


85

Đang thử TypeScript cho một dự án React và tôi bị mắc lỗi này:

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

Xuất hiện khi tôi cố gắng lọc mảng trong thành phần của mình

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

Cho đến nay, tôi đã xem bài viết "Lập chỉ mục đối tượng trong TypeScript" ( https://dev.to/kingdaro/indexing-objects-in-typescript-1cgi ) vì nó có lỗi tương tự, nhưng tôi đã cố gắng thêm chữ ký chỉ mục vào gõ plotTypesvà tôi vẫn gặp lỗi tương tự.

Mã thành phần của tôi:

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;

Câu trả lời:


83

Điều này xảy ra vì bạn cố gắng truy cập thuộc plotOptionstính bằng chuỗi name. TypeScript hiểu rằng namecó thể có bất kỳ giá trị nào, không chỉ tên thuộc tính từ plotOptions. Vì vậy, TypeScript yêu cầu thêm chữ ký chỉ mục vào plotOptions, để nó biết rằng bạn có thể sử dụng bất kỳ tên thuộc tính nào trong đó plotOptions. Nhưng tôi đề nghị thay đổi loại name, vì vậy nó chỉ có thể là một trong các plotOptionsthuộc tính.

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

Bây giờ bạn sẽ chỉ có thể sử dụng các tên thuộc tính tồn tại trong đó plotOptions.

Bạn cũng phải thay đổi một chút mã của mình.

Đầu tiên hãy gán mảng cho một số biến tạm thời, để TS biết kiểu mảng:

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

Sau đó lọc:

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

Nếu bạn đang nhận dữ liệu từ API và không có cách nào để nhập đạo cụ kiểm tra tại thời điểm biên dịch, cách duy nhất là thêm chữ ký chỉ mục vào plotOptions:

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

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

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

Lỗi - lý do cho lỗi là objectkiểu chỉ là một đối tượng trống theo mặc định. Do đó, không thể sử dụng một stringloại để lập chỉ mục {}.

Tốt hơn - lý do lỗi biến mất là vì bây giờ chúng ta đang nói với trình biên dịch objđối số sẽ là một tập hợp các string/anycặp chuỗi / giá trị ( ). Tuy nhiên, chúng tôi đang sử dụng anyloại, vì vậy chúng tôi có thể làm tốt hơn.

Tốt nhất - Tmở rộng đối tượng trống. Umở rộng các phím của T. Do đó Usẽ luôn tồn tại trên T, do đó nó có thể được sử dụng như một giá trị tra cứu.

Đây là một ví dụ đầy đủ:

Tôi đã chuyển thứ tự của các thuốc gốc ( U extends keyof Tbây giờ có trước T extends object) để làm nổi bật rằng thứ tự của thuốc gốc là không quan trọng và bạn nên chọn một thứ tự phù hợp nhất với chức năng của mình.

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'

Cú pháp thay thế

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

1
Tôi đã viết một gói npm nhỏ với chức năng này để làm cho nhiệm vụ này dễ dàng hơn cho những người mới sử dụng Typecript.
Alex Mckay

Gói này có kích thước xấp xỉ 38 byte sau khi được đóng gói và rút gọn.
Alex Mckay

4

Khi chúng ta làm điều gì đó như thế này obj [key] Typescript không thể biết chắc chắn liệu khóa đó có tồn tại trong đối tượng đó hay không. Tôi đã làm gì:

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

4

Tôi sử dụng cái này:

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

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

2

Khi sử dụng Object.keys, các tác dụng sau:

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

0

Không có typescriptlỗi

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

0

Cảm ơn Alex Mckay, tôi đã có một quyết tâm để thiết lập động một đạo cụ:

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

0

Tôi đã thực hiện một số thay đổi nhỏ đối với chức năng / cách sử dụng của Alex McKay mà tôi nghĩ giúp dễ dàng hơn một chút để theo dõi tại sao nó hoạt động và cũng tuân thủ quy tắc không sử dụng trước khi xác định .

Đầu tiên, xác định hàm này để sử dụng:

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

Theo cách tôi đã viết nó, hàm chung cho hàm liệt kê đối tượng đầu tiên, sau đó là thuộc tính trên đối tượng thứ hai (những điều này có thể xảy ra theo bất kỳ thứ tự nào, nhưng nếu bạn chỉ định U extends key of Ttrước khi T extends objectphá vỡ no-use-before-definequy tắc, và nó cũng có ý nghĩa để có đối tượng đầu tiên và thuộc tính 'của nó thứ hai. Cuối cùng, tôi đã sử dụng cú pháp hàm phổ biến hơn thay vì các toán tử mũi tên ( =>).

Dù sao, với những sửa đổi đó, bạn chỉ có thể sử dụng nó như sau:

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

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

getKeyValue(user, "name")

Mà, một lần nữa, tôi thấy dễ đọc hơn một chút.


-9

Đây là những gì nó đã làm việc cho tôi. Nó tsconfig.jsoncó một tùy chọn noImplicitAnyđược đặt thành true, tôi chỉ cần đặt nó thành falsevà bây giờ tôi có thể truy cập các thuộc tính trong các đối tượng bằng cách sử dụng chuỗi.


6
Điều này sẽ loại bỏ nghiêm ngặt, sau đó sẽ không có ích gì khi sử dụng bảng chữ nếu chúng ta tiếp tục loại bỏ các hạn chế này.
Joseph Briggs

Tôi không đồng ý @JosephBriggs. Typecript mang lại cho bảng rất nhiều lợi ích khác. Kiểm tra loại là một trong số họ. Thật tuyệt khi bạn có thể chọn tham gia hoặc không tham gia tùy thuộc vào yêu cầu của dự án của bạn.
Zeke

13
Điều này không giải quyết được vấn đề, nó chỉ bỏ qua nó.
thedayturns

1
@Zeke Tôi hiểu bạn đời :) Tôi đang viết gấp. Ý tôi là nếu chúng ta tiếp tục giải quyết các vấn đề bằng cách bảo nó bỏ qua thì ngay từ đầu sẽ chẳng có ích lợi gì. nhưng sau đó tất cả phụ thuộc vào dự án và các quyết định cho mỗi dự án.
Joseph Briggs

Tôi thích điều này hơn ... ngay cả ngôn ngữ loại mạnh như c # cũng có "var". Tham khảo stackoverflow.com/questions/62377614/… ... Một chút được thiết kế để đặt tất cả các loại mã va chạm để truy cập một thuộc tính đơn giản.
Eric
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.