【问题标题】:'string' can't be used to index type '{}'“字符串”不能用于索引类型“{}”
【发布时间】:2019-12-12 11:24:37
【问题描述】:

我有以下 React 组件,它从对象数组生成 HTML 表。应该显示的列是通过tableColumns 属性定义的。

当循环 items 并显示正确的列时,我必须使用 tableColumn 对象 ({item[column.key]}) 中的 key 属性,但 typescript 会生成以下错误:

元素隐式具有“任何”类型,因为“字符串”类型的表达式不能用于索引类型“{}”。 在“{}”类型上找不到带有“字符串”类型参数的索引签名。

我能做些什么来解决这个问题?我迷路了

我如何调用组件:

<TableGridView
  items={[
    {
      id: 1,
      name: 'John Doe',
      email: 'john@doe.de'
    },
    {
      id: 2,
      name: 'Lorem ipsum',
      email: 'lorem@ipsum.com',
    }
  ]}
  tableColumns={[
    {
      key: 'id',
      label: 'ID',
    },
    {
      key: 'name',
      label: 'Name',
    }
  ]}
/>

我的组件:

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

【问题讨论】:

    标签: javascript reactjs typescript


    【解决方案1】:
      items: object[],
    

    虽然从技术上讲它是一个 JavaScript 对象,但类型可以更好。要让 Typescript 在访问对象属性时正确帮助您识别错误,您需要告诉它对象的确切形状。如果您将其键入为object,则打字稿无法帮助您。相反,您可以告诉它对象具有的确切属性和数据类型:

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

    现在,如果对象可以包含任何类型的键/值对,您至少可以通过使用对象索引类型告诉 typescript 值(和键)具有什么类型:

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

    在给定的情况下,这将是准确的类型。

    【讨论】:

    • 很抱歉,您的回答并没有解释问题,只是说明它很糟糕。请考虑添加解释。人们希望能够在对象上使用字符串索引,而不必显式定义它。
    • @maksion 人们会期望 ...我不会? Typescript 应该如何知道 object[string] 的类型?它是any,因此它是一种不好的类型。
    • 谢谢你!请原谅我的 nit pic: items: { [key: string]: number |细绳; //
    • 对象索引的类型只能是字符串(如果我错了,请纠正我),所以看起来是多余的。
    • @papiro 不,还有符号(和数字,从打字稿的角度来看)
    【解决方案2】:

    如果它是一个迂腐的 javascript 对象,为每个字段创建类型定义没有意义,并且作为类定义没有意义,您可以使用 any 键入该对象,然后 typescript 会让您索引你想要的。

    例如

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

    【讨论】:

    • 因为非常丑陋的 TypeScript 解决方法而投反对票。
    • 赞成,因为 Typescript 通常很有用,但有时会像 OP 的问题一样阻碍。你可以花一整天的时间尝试输入所有最后的东西(而不是提供任何东西),或者采取合理的中间道路“在有帮助、有意义时使用它”。最后请记住,在运行时它是 JavaScript,无论您使用类型系统有多可爱,并且您的类型定义实际上可能无法反映通过代码运行的实际情况(尤其是涉及 API 调用时!)
    【解决方案3】:

    使用泛型

    // 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'
    

    【讨论】:

    • 不要使用object 作为类型。 object 类型目前很难使用 (see this issue)。考虑改用Record&lt;string, unknown&gt;,因为它可以让您更轻松地检查和使用keys.eslint@typescript-eslint/ban-types)
    【解决方案4】:

    首先,将items 的类型定义为objects 的数组是一个坏主意,因为它违背了Typescript 的目的。而是定义数组将包含什么类型的对象。我会这样做:

    type Item = {
      id: number,
      name: string,
      email: string,
    }
    
    export type TableGridViewProps = {
      items: Item[],
      tableColumns: TableColumn[]
    };
    

    之后,如果您完全确定 key 将存在于 item 中,您可以执行以下操作来告诉 Typescript 您正在访问一个有效的索引。

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

    【讨论】:

    • 为什么你认为它“违背了打字稿的目的?”
    • @Kraken 也许我措辞错误,但我想说的是,如果你已经知道对象的形状,那么作为一个好习惯,你不应该只是把它 object快速解决方法。这样做你会牺牲很多 Typescript 带来的好处
    • 啊,我明白了。我想我可以澄清一下。
    • 考虑到这是一个通用的表格组件,其中键是在道具中的每列定义的,不太可能事先知道每个项目的形状,或者至少每次都相同。如果项目形状不可知,可能会考虑将项目键入为 { [key: string]: string | number }
    • “keyof”关键字在我的情况下起到了作用,谢谢!
    【解决方案5】:

    尝试添加类型 any[]

    items:any[]

    【讨论】:

      猜你喜欢
      • 2021-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-04
      • 2021-05-26
      • 2022-11-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多