想象一下我想添加类型的这个JS函数示例:

const remap = (obj) => {
  const mapped = {};
  Object.keys(obj).forEach((key) => {
    mapped[key] = !!key;
  });

  return mapped;
};

我正在try add types via a generic (in this TS playground),但我不断遇到这个错误:

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的类型并允许我写它,但它不会.

返回时使用as是让TS接受这个的唯一方法吗?

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

推荐答案

The underlying reason for the error is that Object.keys(x) is declared in the TS library to return string[] and not something like (keyof typeof x)[]. This is intentional; see Why doesn't Object.keys return a keyof type in TypeScript?. So when you index mapped[key], you are doing so with a key of string and not necessarily with a key of Mapped<T>. And therefore, it is technically unsafe to write a boolean to it, because maybe you're writing to a key that Mapped<T> doesn't know about, and you can't be sure that key would accept a boolean. You'd need to either make Mapped<T> just be {[k: string]: boolean} (which means T is unnecessary) or you need to assert that what you're doing is safe.

请注意,TypScript会让您从mapped[key]获得readboolean,尽管这在技术上也是不安全的:

Object.keys(obj).forEach((key) => {
  const test = mapped[key]; // boolean | undefined
});

这就是TypScript. 无论如何,这就是为什么您会收到错误消息,即Mapped<T>只能被索引(与string一起)以供阅读. 它曾经只是说根本不能用string来索引Mapped<T>,但由于如上所示这显然是不真实的,因此他们将错误消息更改为新措辞. 有关更多信息,请参阅microsoft/TypeScript#47357.


无论如何,断言的正常方法是说您有信心Object.keys(obj)将返回(keyof T)[],尽管TypScript保留可能会存在其他键. 如果您这样做:

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

那么它就像所写的那样起作用.TypScript很高兴允许mapped[key]属于Mapped<T>[keyof Mapped<T>]类型,即boolean | undefined,因此它将接受boolean.

Playground link to code

Typescript相关问答推荐

从传递的数组中出现的值设置返回键的类型

自定义挂钩上的React useState正在忽略提供的初始状态

物料UI图表仅在悬停后加载行

为什么在TypScript中将类型守护的返回类型写成This is(

脉轮ui打字脚本强制执行图标按钮的咏叹调标签-如何关闭

使用动态主体初始化变量

我可以以这样一种方式键入对象,只允许它的键作为它的值吗?

有什么方法可以类型安全地包装import()函数吗?

在排版修饰器中推断方法响应类型

TypeScrip中的类型安全包装类

从子类集合实例化类,同时保持对Typescript中静态成员的访问

找不到财产的标准方式?

类型';字符串|数字';不可分配给类型';未定义';.类型';字符串';不可分配给类型';未定义';

专用路由组件不能从挂钩推断类型

是否可以强制静态方法将同一类型的对象返回其自己的类?

为什么上下文类型在`fn|curred(Fn)`的联合中不起作用?

具有匹配功能的TS通用事件处理程序

当传递带有或不带有参数的函数作为组件props 时,我应该如何指定类型?

为什么受歧视的unions 在一种情况下运作良好,但在另一种非常类似的情况下却不起作用?

如何让Record中的每个值都有独立的类型?