【问题标题】:Why do parentheses affect type narrowing in TypeScript?为什么括号会影响 TypeScript 中的类型缩小?
【发布时间】:2021-12-27 02:44:06
【问题描述】:

在打字稿中:

let str: string = 'abc';
let val: unknown = 'test';

if (typeof val === 'string') {
    str = val;
} 
// this code does not report any error, everything works fine.

但是,如果我稍微更改一下代码:

if ((typeof val) === 'string') { 
    str = val; 
} 
// add the () to hold typeof val;
// error report in typescript in this line: str = val !

TS Playground link

这真的让我很困惑,谁能帮忙解释一下这里发生了什么。

【问题讨论】:

  • let val: unknow 不起作用 - 你需要一个 n
  • 这能回答你的问题吗? 'unknown' vs. 'any'
  • @jsejcksn 不要认为是这样 - 这是关于 unknown 的特殊怪癖或 TS 解释器如何使用括号,而不是与 any 进行比较
  • @CertainPerformance 甚至可能与类型保护有关,甚至与unknown 无关。编辑:嗯,不,它似乎不是。使其成为let val: string | undefined 不会表现出相同的行为。 EDIT2:好吧,我的错。这实际上是一个类型保护问题:tsplay.dev/WK7VKW
  • 我认为这与 typeof 关键字的歧义有关,并且 TS 不再能够将其识别为类型保护。歧义:type T = typeof val; vs let t = typeof val; 我猜括号把它变成了后者,这样你的条件就从类型保护降级为两个字符串的比较,与你写 if(t === "string") {...} 没有什么不同跨度>

标签: typescript type-narrowing


【解决方案1】:

TypeScript 的 typeof type guards 走得很好。 typeof val 是一个字符串,你可以对它进行任意字符串操作,但是typeof val === "string" 是一个特殊的构造,当表达式为true 时,它会缩小val 的类型。因此,TypeScript 被明确编程为匹配 typeof ${reference} ${op} ${literal}${literal} ${op} typeof ${reference}(对于 op = ==!====!==),但 typeof ${reference} 没有内置的括号容差(是 SyntaxKind.ParenthesizedExpression 而不是 SyntaxKind.TypeOfExpression)、字符串操作或其他任何东西。

TypeScript 负责人 Ryan Cavanaugh 在 microsoft/TypeScript#42203 中描述了这一点,“typeof 类型缩小的行为与等效括号分组不同”,感谢 jcalz 提供的链接:

缩窄只发生在预定义的句法模式上,这不是其中之一。不过,为了清楚起见,我可以看到想要在此处添加括号 - 我们也应该检测到这个。

这听起来像是未来修复的候选者,尽管即使添加了该模式,您仍然会在一定程度上受限于用作类型保护的 typeof 表达式的复杂性。


来自编译器源microsoft/TypeScript main/src/compiler/checker.ts,我的cmets:

function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
  switch (expr.operatorToken.kind) {
    // ...
    case SyntaxKind.EqualsEqualsToken:
    case SyntaxKind.ExclamationEqualsToken:
    case SyntaxKind.EqualsEqualsEqualsToken:
    case SyntaxKind.ExclamationEqualsEqualsToken:
        const operator = expr.operatorToken.kind;
        const left = getReferenceCandidate(expr.left);
        const right = getReferenceCandidate(expr.right);
        // Check that the left is typeof and the right is a string literal...
        if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) {
            return narrowTypeByTypeof(type, left as TypeOfExpression, operator, right, assumeTrue);
        }
        // ...or the opposite...
        if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) {
            return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue);
        }
        // ...or skip it and move on. Don't bother trying to remove parentheses
        // or doing anything else clever to try to make arbitrary expressions work.
        if (isMatchingReference(reference, left)) {
            return narrowTypeByEquality(type, operator, right, assumeTrue);
        }
        if (isMatchingReference(reference, right)) {
            return narrowTypeByEquality(type, operator, left, assumeTrue);
        }
        // ...
  }
  return type;
}

【讨论】:

  • 你也可以参考ms/TS#42203
  • @jcalz 不错的发现!我已经在正文中添加了它和相关的引用。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-11
  • 2013-04-05
  • 2015-05-04
  • 2022-12-23
  • 1970-01-01
  • 1970-01-01
  • 2019-11-29
相关资源
最近更新 更多