为啥括号会影响 TypeScript 中的类型缩小?
Posted
技术标签:
【中文标题】为啥括号会影响 TypeScript 中的类型缩小?【英文标题】: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") ...
没有什么不同跨度>
【参考方案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 不错的发现!我已经在正文中添加了它和相关的引用。谢谢!以上是关于为啥括号会影响 TypeScript 中的类型缩小?的主要内容,如果未能解决你的问题,请参考以下文章