我有以下 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>
);
}
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,
}[]
在给定的情况下,这将是准确的类型。
如果它是一个迂腐的 javascript 对象,为每个字段创建类型定义没有意义,并且作为类定义没有意义,您可以使用 any
键入对象,打字稿会让您根据需要进行索引。
前任。
//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;
}
使用泛型
// 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<string, unknown>
,因为它可以让您更轻松地检查和使用 keys.eslint@typescript-eslint/ban-types)
首先,将 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>
object
。这样做你会牺牲 Typescript 带来的很多好处
{ [key: string]: string | number }
。
尝试添加类型 any[]
items:any[]