想象一下我想为其添加类型的示例 JS 函数:
const remap = (obj) => {
const mapped = {};
Object.keys(obj).forEach((key) => {
mapped[key] = !!key;
});
return mapped;
};
我正在尝试通过泛型(在这个 TS 游乐场中)添加类型,但我一直遇到此错误:
Type 'Mapped<T>' is generic and can only be indexed for reading.(2862)
type Mapped<T> = {
[K in keyof T]?: boolean;
};
const remap = <T extends Record<string, unknown>>(
obj: T
) => {
const mapped: Mapped<T> = {};
Object.keys(obj).forEach((key) => {
mapped[key] = !!key; // Type 'Mapped<T>' is generic and can only be indexed for reading.(2862)
});
return mapped;
};
我想了解为什么 TS 不允许我写入这个泛型类型的对象,以及是否有其他方法可以解决它。我希望 TS 能够理解
mapped
的类型并允许我写入它,但它不会。
只有让TS接受这个才能在返回时使用
as
吗?
const remapWithAs = <T extends Record<string, unknown>>(
obj: T
) => {
const mapped: Record<string, boolean> = {};
Object.keys(obj).forEach((key) => {
mapped[key] = !!key;
});
return mapped as Mapped<T>; // Is this my only option?
};
Object.keys(x)
在TS库中声明为返回string[]
而不是类似
(keyof typeof x)[]
之类的东西。这是故意的;请参阅为什么 Object.keys 在 TypeScript 中不返回 keyof 类型?。因此,当您索引
mapped[key]
时,您使用的是
string
键,而不一定是
Mapped<T>
键。因此,从技术上讲,向其写入
boolean
是不安全的,因为也许您正在写入
Mapped<T>
不知道的密钥,并且您无法确定that 键会接受
boolean
。您需要使
Mapped<T>
只是
{[k: string]: boolean}
(这意味着
T
是不必要的),或者您需要断言 您正在做的事情是安全的。 请注意,TypeScript 会让您从
boolean
读取
mapped[key]
,尽管这在技术上也是不安全的:
Object.keys(obj).forEach((key) => {
const test = mapped[key]; // boolean | undefined
});
TypeScript 就是这样。无论如何,这就是为什么您收到错误消息:Mapped<T>
只能被索引(使用
string
)以供阅读。它过去只是说你根本无法用
Mapped<T>
索引
string
,但由于这显然是不正确的,如上所示,他们将错误消息更改为新的措辞。请参阅microsoft/TypeScript#47357 了解更多信息。
Object.keys(obj)
将返回
(keyof T)[]
,尽管 TypeScript 保留可能会出现其他键。如果你这样做:
const remap = <T extends Record<string, unknown>>(
initialState: T
) => {
const mapped: Mapped<T> = {};
(Object.keys(initialState) as (keyof T)[]).forEach(key => {
mapped[key] = !!key; // okay
});
return mapped;
};
然后它就按照写的那样工作了。 TypeScript 很乐意允许 mapped[key]
属于
Mapped<T>[keyof Mapped<T>]
类型,即
boolean | undefined
,因此它会接受
boolean
。