javascript - 'string' tidak bisa digunakan untuk mengindeks tipe '{}'


Saya memiliki komponen React berikut yang menghasilkan Tabel HTML dari sebuah array objek. Kolom-kolom yang harus ditampilkan didefinisikan melalui properti tableColumns.

Saat melakukan perulangan melalui items dan menampilkan kolom yang benar, saya harus menggunakan properti key dari objek tableColumn ({item[column.key]}) tetapi TypeScript menghasilkan error berikut:

Elemen secara implisit memiliki tipe 'any' karena ekspresi dengan tipe 'string' tidak dapat digunakan untuk mengindeks tipe '{}'. Tidak ditemukan tanda indeks dengan parameter tipe 'string' pada tipe '{}'.

Apa yang bisa saya lakukan untuk memperbaiki ini? Saya bingung

Bagaimana saya memanggil komponen:

<TableGridView
  items={[
    {
      id: 1,
      name: 'John Doe',
      email: '[email '
    },
    {
      id: 2,
      name: 'Lorem ipsum',
      email: '[email ',
    }
  ]}
  tableColumns={[
    {
      key: 'id',
      label: 'ID',
    },
    {
      key: 'name',
      label: 'Name',
    }
  ]}
/>

Komponen Saya:

export type TableColumn = {
  key: string,
  label: string,
};

export type TableGridViewProps = {
  items: object[],
  tableColumns: TableColumn[]
};

const TableGridView: React.FC<TableGridViewProps> = ({ tableColumns, items }) => {
  return (
    <table>
      <tbody>
        {items.map(item => {
          return (
            <tr>
              {tableColumns.map((column, index) => {
                return (
                  <td
                    key={column.key}
                    className="lorem ipsum"
                  >
                    {item[column.key]} // error thrown here
                  </td>
                );
              })}
            </tr>
          );
        })}
      </tbody>
    </table>
  );
}

Jawaban #1

  items: object[],

Meskipun secara teknis ini adalah objek JavaScript, tipenya bisa lebih baik. Agar Typescript bisa membantu Anda dengan benar dalam mengidentifikasi kesalahan saat mengakses properti objek, Anda perlu memberitahunya bentuk objek yang tepat. Jika Anda mengetiknya sebagai object, typescript tidak bisa membantu Anda dengan itu. Sebagai gantinya, Anda bisa memberitahunya properti dan tipe data yang tepat dari objek tersebut:

  let assistance: { safe: string } = { safe: 1 /* typescript can now tell this is wrong */ };
  assistance.unknown; // typescript can tell this wont really work too

Sekarang dalam kasus di mana objek dapat berisi pasangan kunci/nilai apa pun, Anda setidaknya dapat memberitahu TypeScript tipe nilai (dan kuncinya), dengan menggunakan tipe indeks objek:

 items: {
   [key: string]: number | string,
  }[]

Itu akan menjadi jenis yang akurat dalam kasus yang diberikan.

Jawaban #2

Pertama-tama, mendefinisikan tipe items sebagai array dari objects adalah ide yang buruk, karena ini mengalahkan tujuan dari Typescript. Sebagai gantinya, definisikan tipe objek apa yang akan dikandung oleh array tersebut. Saya akan melakukannya dengan cara ini:

type Item = {
  id: number,
  name: string,
  email: string,
}

export type TableGridViewProps = {
  items: Item[],
  tableColumns: TableColumn[]
};

Setelah itu, jika Anda yakin sepenuhnya bahwa key akan ada di item, Anda dapat melakukan hal berikut untuk memberi tahu Typescript bahwa Anda mengakses indeks yang valid.

<td
  key={column.key}
  className="lorem ipsum"
>
  {item[column.key as keyof typeof Item]}
</td>

Jawaban #3

Jika itu adalah objek JavaScript yang bersifat pedantik dan tidak masuk akal untuk membuat definisi tipe per bidang, dan tidak masuk akal sebagai definisi kelas, Anda dapat mengetik objek dengan any dan TypeScript akan membiarkan Anda mengindeks sesuka hati.

mis.

//obviously no one with two brain cells is going to type each field individually
let complicatedObject: any = {
    attr1: 0,
    attr2: 0,
    ...
    attr999: 0
}

Object.keys(complicatedObject).forEach(key => {
    complicatedObject[key] += 1;
}

Jawaban #4

Gunakan Generik

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

Buruk - alasan terjadinya kesalahan adalah tipe object secara default hanyalah objek kosong. Oleh karena itu tidak mungkin menggunakan tipe string untuk mengindeks {}.

Lebih baik - alasan kesalahan hilang adalah karena sekarang kita memberi tahu compiler bahwa argumen obj akan menjadi koleksi pasangan string/nilai (string/any). Namun, kita menggunakan tipe any, jadi kita bisa melakukan lebih baik.

Terbaik - T memperluas objek kosong. U memperluas kunci dari T. Oleh karena itu U akan selalu ada pada T, sehingga dapat digunakan sebagai nilai pencarian.

Berikut adalah contoh lengkap:

Saya telah menukar urutan generik (U extends keyof T sekarang datang sebelum T extends object) untuk menyoroti bahwa urutan generik tidak penting dan Anda harus memilih urutan yang paling masuk akal untuk fungsi Anda.

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'

Artikel terkait