我正在制作一个向数组的每个元素添加附加属性的函数:
type AnyObj = { [key: string]: any };
function addIndexProp<T extends AnyObj>(
obj: T[],
myProp: string
): T[] {
return obj.map(item => {
item[myProp] = 'myProp';
return item;
});
}
但是我遇到了以下错误:
$ tsc --noEmit src/test.ts
src/test.ts:8:5 - error TS2536: Type 'string' cannot be used to index type 'T'.
8 item[myProp] = 'myProp';
~~~~~~~~~~~~
Found 1 error.
error Command failed with exit code 1
我不明白为什么会这样,因为我使用字符串索引类型指定了T,尤其是以下代码能很好地工作:
const test: { [key: string]: any } = { test: 1 };
const myProp = 'prop';
test[myProp] = 'myProp';
有人知道为什么第一个代码片段引发错误,以及如何解决该问题吗?谢谢!
答案 0 :(得分:1)
T extends AnyObj
的问题在于,T
可以是值AnyObj
可分配的任何东西,并且TypeScript将implicit index signatures赋予属性匹配的对象文字类型索引签名属性。因此,尽管没有索引签名,但可以将{a: number}
之类的类型分配给AnyObj
。因此,您可以编写以下代码而不会出现错误:
let obj = { a: 123, b: 345 };
const addedProps = addIndexProp([obj], "a");
addedProps[0].a.toFixed(); // okay at compile time, error at runtime
在addIndexProp()
内,有问题的行最终将值"myProp"
分配给应该为number
类型的属性。那就不好了。
正确的解决方案实际上取决于您的用例。如果您只是想让编译器满意而又没有使代码更安全,那么可以使用type assertion。即使item[myProp]="myProp"
破坏了现有属性,这也可以让您分配myProp
:
function addIndexPropAssert<T extends AnyObj>(
obj: T[],
myProp: string
): T[] {
return obj.map(item => {
(item as any)[myProp] = 'myProp';
return item;
});
}
最安全的做法是不对原始obj
元素进行突变,而使用更具表现力的类型来表示您正在添加或覆盖{ {1}}:
[myProp]
在这里,我们使用T
,其中目标是一个新对象,因此不会修改任何现有对象。该类型最终将是function addIndexPropDoNotModify<T, K extends PropertyKey>(obj: T[], myProp: K) {
return obj.map((item: Omit<T, K>) => expandType(Object.assign(
{ [myProp]: "myProp" } as Record<K, string>,
item
)));
}
(意味着Object.assign()
属性具有被添加回一个Omit<T, K>
值)。
T
只是一个帮助函数,它说服编译器将诸如[myProp]
之类的映射类型的丑陋交集转换为诸如Record<K, string>
之类的更简单的类型,如下所示:
[myProp]
这是它的工作方式:
string
所以这里的区别在于,尽管expandType()
的类型为Omit<{a: number, b: number}, "a"> & Record<"a", string>
,现在{a: string, b: number}
的元素仍被视为类型function expandType<T>(x: T) {
return x as any as T extends infer U ? { [K in keyof U]: T[K] } : never;
}
。
无论如何,希望其中之一能给您一些指导。祝你好运!