我有一个对象类型,其键的对应值是数字类型、字符串类型、可选数字类型或可选字符串类型:

interface MyObject {
  mandatoryNumber: number;
  optionalNumber?: number;
  mandatoryString: string;
  optionalString?: string;
}

我如何编写一个函数,该函数将此对象的实例作为参数和键,仅限制类型为Numbers或可选Numbers的键,如果定义了值,则返回两倍的值,否则返回0?

我try 了以下几种方法:

type NumberKeys<T> = {
  [K in keyof T]: T[K] extends number ? K : never
}[keyof T];

function process<K extends NumberKeys<MyObject>>(obj: MyObject, key: K): number {
    const value = obj[key]; // -> Typescript error: Type 'K' cannot be used to index type 'MyObject'
    return value !== undefined ? value * 2 : 0;
}

但我收到一个打字错误:类型‘K’不能用于索引类型‘MyObject’.

推荐答案

主要的问题是,您对NumberKeys<T>的定义使用了homomorphicmapped type(参见What does "homomorphic mapped type" mean?),其中包括保留属性是否为optional.因此,如果T甚至有一个可选属性,那么映射的类型将在其属性中包含undefined,而这undefined将使其一直进入结果:

type Ex = NumberKeys<{ a: number, b?: string }>;
// type Ex = "a" | undefined

undefined把事情搞砸了,编译器抱怨K不能索引MyObject,因为K可以是undefined.因此,我们需要做的最大的修复就是压制undefined.一种方法是使用the mapping modifier -?使结果中的所有属性都是必需的:

type NumberKeys<T> = {
  [K in keyof T]-?: T[K] extends number ? K : never
}[keyof T];

type Ex = NumberKeys<{ a: number, b?: string }>;
// type Ex = "a"

这很好,它解决了你的错误:

function process<K extends NumberKeys<MyObject>>(obj: MyObject, key: K): number {
  const value = obj[key]; // okay
  return value !== undefined ? value * 2 : 0;
}

不幸的是,你仍然没有得到你想要的行为:

type NKMO = NumberKeys<MyObject>;
// type NKMO = "mandatoryNumber"

undefined走了,但optionalNumber不在.这是因为映射修改器-?似乎只从映射结果中移除undefined,而不从原始属性中移除.这被认为是TypeScrip中的一个错误,就像在microsoft/TypeScript#3optionalNumber5中报告的那样,但这是一个如此长期存在的错误,我预计它永远不会得到修复.我想,如果他们修复了它,很多现实世界的代码都会被破解.

无论如何,这意味着即使有-?,optionalNumber‘S属性仍被视为number | undefined,并且由于number | undefined不会扩展number,因此您的代码中不会 Select 键.解决这个问题的最简单方法是只判断number | undefined,而不是number:

type NumberKeys<T> = {
  [K in keyof T]-?: T[K] extends number | undefined ? K : never
}[keyof T];

type NKMO = NumberKeys<MyObject>;
// type NKMO = "mandatoryNumber" | "optionalNumber"

很有效.


或者,你可以通过在Required<MyObject>而不是MyObject上操作来回避这个问题,使用the Required utility type(当然,它是通过-?实现的):

type NKMO = NumberKeys<Required<MyObject>>;
// type NKMO = "mandatoryNumber" | "optionalNumber"

function process<K extends NumberKeys<Required<MyObject>>>(
  obj: MyObject, key: K): number {
  const value = obj[key]; // okay
  return value !== undefined ? value * 2 : 0;
}

这与另一种方法非常相似;两种方法都处理引入undefined的可选属性,只是方式略有不同.

Playground link to code

Typescript相关问答推荐

React router 6.22.3不只是在生产模式下显示,为什么?

当我点击外部按钮时,如何打开Html Select 选项菜单?

如何以Angular 形式显示验证错误消息

是否使用非显式名称隔离在对象属性上声明的接口的内部类型?

为什么在回调函数的参数中不使用类型保护?

如何将泛型对象作为返回类型添加到类型脚本中

记录键的泛型类型

转换器不需要的类型交集

TYPE FOR ARRAY,其中每个泛型元素是单独的键类型对,然后在该数组上循环

如何在脚本中输入具有不同数量的泛型类型变量的函数?

将布尔值附加到每个对象特性

如何在Deno中获取Base64图像的宽/高?

如何在TypeScrip中使用数组作为字符串的封闭列表?

窄SomeType与SomeType[]

TypeScript:如何使用泛型和子类型创建泛型默认函数?

导航链接不触发自定义挂钩

必须从注入上下文调用Angular ResolveFn-inject()

无法使用prisma中的事务更新记录

是否可以从 TypeScript 的类型推断中排除this?

类型为 `(callback: (...args: T) => void, params: T)` 的函数的泛型