我正在创建一个函数,为数组的每个元素添加一个附加属性:
type AnyObj = { [key: string]: any };
function addIndexProp<T extends AnyObj>(
obj: T[],
myProp: string
): T[] {
return obj.map(item => {
item[myProp] = 'myProp';
return item;
});
}
Run Code Online (Sandbox Code Playgroud)
但我收到以下错误:
$ 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
Run Code Online (Sandbox Code Playgroud)
我不明白为什么会发生这种情况,因为我已经使用字符串索引类型指定了 T,尤其是以下代码运行良好:
const test: { [key: string]: any } = { test: 1 };
const myProp = 'prop';
test[myProp] = 'myProp';
Run Code Online (Sandbox Code Playgroud)
有谁知道为什么第一个片段会抛出错误,以及如何解决该问题?谢谢!
问题是T extends AnyObj,它可以是可分配T值的任何内容,并且 TypeScript 将为属性与索引签名属性匹配的对象文字类型提供隐式索引签名。因此,尽管没有索引签名,但类似的类型可以分配给。所以你可以编写如下代码,不会出现错误:AnyObj{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
Run Code Online (Sandbox Code Playgroud)
在内部addIndexProp(),有问题的行最终将一个值分配"myProp"给一个应该是类型的属性number。那不好。
正确的修复实际上取决于您的用例。如果您只是想让编译器满意而不实际使代码更安全,则可以使用类型断言。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;
});
}
Run Code Online (Sandbox Code Playgroud)
最安全的做法是不要改变原始obj元素并使用更具表现力的类型来指示您要添加或覆盖[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
)));
}
Run Code Online (Sandbox Code Playgroud)
在这里,我们使用的Object.assign()目标是新对象,因此不会修改现有对象。Omit<T, K>该类型最终将成为(意味着删除了T属性[myProp])与Record<K, string>(意味着[myProp]属性已被添加回值)的交集数组string。
AndexpandType()只是一个辅助函数,它说服编译器将映射类型的丑陋交集转变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;
}
Run Code Online (Sandbox Code Playgroud)
它的工作原理如下:
let obj2 = { a: 123, b: 345 }; // {a: number, b: number}
const addedProps2 = addIndexPropDoNotModify([obj2], "a"); // Array<{a: string, b: number}>
addedProps2[0].a.toFixed(); // caught error at compile time, toFixed does not exist on string
Run Code Online (Sandbox Code Playgroud)
所以这里的区别在于addedProps2's 元素现在被视为是类型,{a: string, b: number}即使obj是类型{a: number, b: number}。
无论如何,希望其中之一能给您一些指导。祝你好运!