Typescript:当key存储在变量中时,类型收缩不适用于“in”

gijlo24d  于 2023-06-24  发布在  TypeScript
关注(0)|答案(3)|浏览(111)

考虑这个简单的片段。我也把它贴在这里:

type A =
  | {
      b: number;
    }
  | {
      c: number;
    };

function f1(a: A) {
  if ('b' in a) {
    return a['b']; // No problem!
  }
  return 42;
}

function f2(a: A) {
  const key = 'b';
  if (key in a) {
    return a[key];  // Property 'b' does not exist on type 'A'
  }
  return 42;
}

为什么a的类型没有缩小到{b: number}中的f2?(与f1相同)

nhn9ugyo

nhn9ugyo1#

这本质上与microsoft/TypeScript#10530的问题相同;控制流分析中的类型缩小只会发生在直接为文字的属性(如"b"),而不会发生在类型为文字类型的任意表达式中。第10530期讨论通过财产访问缩小...像a.ba["b"],这 * 确实 * 导致a变窄,而a[key],这 * 不 *。
正如您所注意到的,in操作符类型保护也会发生这种情况(如microsoft/TypeScript#15256中实现的),其中"b" in a缩小了a的类型,但key in a没有。在#10530中没有明确提到这一点(它早于in类型保护),但我不认为有另一个存在的问题。
根据microsoft/TypeScript#10565,最初尝试解决#10530中的问题,为文字类型的任意表达式添加类型保护功能会显着恶化编译器性能。也许对所有x in y检查执行额外的分析比对所有y[x]属性访问执行额外的分析要便宜,但至少到目前为止没有人关心。
你可以随时在GitHub上打开你自己的问题(许多问题最终都是重复的,我不能100%确定这不会被认为是#10530的重复,或者没有其他问题重复),但实际上,它可能不会很快改变。
如果您想要一个变通方法来解决无法用字符串文字替换key的情况,您可以编写自己的用户定义的类型保护函数hasProp(obj, prop)。实现只返回prop in obj,但它的类型签名显式地表明true结果应该导致obj缩小到那些键类型为prop的联合成员:

function hasProp<T extends object, K extends PropertyKey>(
    obj: T, prop: K
): obj is Extract<T, { [P in K]?: any }> {
    return prop in obj;
}

然后在函数中,将key in a替换为hasProp(a, key)

function f3(a: A) {
    const key = 'b';
    if (hasProp(a, key)) {
        return a[key];  // okay
    }
    return 42;
}

Playground链接到代码

nlejzf6q

nlejzf6q2#

你需要像这样的自定义类型保护函数:

function isin<T>(key: PropertyKey, obj: T): key is keyof T {
  return key in obj;
}

TypeScript不会缩小存储在变量中的键的类型,显然是出于性能原因。
有关另一种方法以及为什么TypeScript不这样做的详细原因,请参阅以下答案:https://stackoverflow.com/a/64618261/13651701

qrjkbowd

qrjkbowd3#

在旧版本的3.6.3中,上述两个现有的答案都不适用于我。由于我的目标环境,我被困在旧版本中。请看下面我的工作版本:

/**
 * Type guard function to check if a property exists in an object.
 * 
 * @template T - The type of the object. Must extend object.
 * 
 * @param {PropertyKey} key - The property key to check.
 * @param {T} obj - The object in which to check for the property.
 * 
 * @returns {boolean} - True if the property exists in the object, false otherwise.
 * 
 * If true, the TypeScript type of `key` is narrowed to be a keyof `T`, 
 * indicating it exists as a property within the object `T`.
 */
export function is_in<T extends Object>(
    key: PropertyKey, obj: T
): key is keyof T {
    return key in obj;
}

相关问题