如果将键写入字符串文本,我可以使用TypScript的in关键字以类型安全的方式判断对象是否具有键:

function guardHasTest <Data extends object> (
  value: Data
): Data & Record<'test', unknown> {
  if (!('test' in value)) {
    throw new Error('Missing key')
  },
  return value
}

然而,如果我将键设为动态字符串,则相同的语法不会缩小类型范围:

function guardHasKey <Data extends object, Key extends string> (
  value: Data,
  key: Key
): Data & Record<Key, unknown> {
  if (!(key in value)) {
    throw new Error('Missing key')
  }
  return value
  // Type 'Data' is not assignable to type 'Data & Record<Key, unknown>'.
  Type 'object' is not assignable to type 'Data & Record<Key, unknown>'.
    Type 'object' is not assignable to type 'Data'.
      'object' is assignable to the constraint of type 'Data', but 'Data' could be instantiated with a different subtype of constraint 'object'.
        Type 'Data' is not assignable to type 'Record<Key, unknown>'.
          Type 'object' is not assignable to type 'Record<Key, unknown>'.ts(2322)
}

我不想使用自定义类型保护,因为它们的逻辑不是类型安全的:

export function isKeyOf<T, const Key extends string>(
  obj: T,
  key: Key
): obj is T & Record<Key, unknown> {
  return true // No error, even though the logic is not correct
}

如何让函数以完全类型安全的方式动态判断对象是否具有键?

推荐答案

guardHasTest()之所以有效,是因为它使用了对unlisted property narrowing with the in operator的支持,正如在microsoft/TypeScript#50666中实现并随TypScript 4.9一起发布的那样.在4.9之前的TypScript版本中,这会给您带来与guardHasKey()中相同的错误. 这是一个相对较新的功能.

Microsoft/Typescript#50666上的PR旨在解决microsoft/TypeScript#21732处的功能请求.它处理literal type(例如"test")的键.它专门用not来处理generic键的情况:

我将这个PR标记为修复#21732,尽管它没有解决key in obj的情况,其中key是某种通用类型.一般来说,当key是某种非有限类型(例如string)时,我们在key in obj判断后可以有意义地反映的唯一影响是obj[key]应该是有效的表达(类型unknown),前提是keyobj都是已知自判断以来没有被修改的.这可能可以在if陈述或三进制操作符的真分支中做到,但在所有控制流场景中不可能做到.

这是关于为什么没有这样做的官方说法;人们认为不可能以一种适用于出现control flow analysis的所有必要位置的方式为非字面键实现in个锯齿. 如果您想在这里看到一些不同的东西,您可以为其提交功能请求,但我对这种情况的发生不会抱有很高的期望.


这意味着目前不可能让编译器简单地注意到guardHasKey()像广告中所说的那样工作. (事实上,想象一下guardHasKey(obj, Math.random()<0.5 ? "a" : "b")应该缩小到什么有点有趣.它应该像Record所暗示的那样是typeof obj & {a: unknown; b: unknown}吗?或者应该是typeof obj & ({a: unknown} | {b: unknown}),还是其他什么? 事实证明,这与阻碍修复microsoft/TypeScript#13948的难题是相同的.)

现在,您可以只使用type assertion来强制编译return行,或者可以编写custom type guard function,例如在Microsoft/Typescript#21732中编写的custom type guard function.当然,编译器不能保证这两者是类型安全的.但这就是这些功能的目的,让您表达您知道而编译器不知道的内容.因此,虽然您可能更愿意为了某种类型的纯粹性而避免使用类型保护函数,但要知道,TypScript并不意味着type assertion%纯粹的编译器验证类型安全,自定义类型保护函数是允许您将不安全部分(该函数的实现)与安全部分(该函数的调用者)分开的构建块之一.

Typescript相关问答推荐

创建一个通用上下文,允许正确推断其中的子项

具有映射返回类型的通用设置实用程序

Angular 过滤器形式基于某些条件的数组控制

有没有一种方法可以在保持类型的可选状态的同时更改类型?

如何判断输入是否是TypeScript中的品牌类型?

类型脚本类型字段组合

泛型类型联合将参数转换为Never

Material UI / MUI系统:我如何告诉TypeScript主题是由提供程序传递的?

如何使用WebStorm调试NextJS的TypeScrip服务器端代码?

如何使我的函数专门针对联合标记?

如果数组使用Reaction-Hook-Form更改,则重新呈现表单

如何在方法中正确地传递来自TypeScrip对象的字段子集?

不带其他属性的精确函数返回类型

Angular material 无法显示通过API获取的数据

TypeScrip:使用Cypress测试包含插槽的Vue3组件

有没有更干净的方法来更新**key of T**的值?

确保财产存在

如何根据Typescript 中带有泛型的对象嵌套键数组获取数组或对象的正确类型?

如何通过nx找到所有受影响的项目?

req.files = 未定义(Multer、Express、Typescript)