为啥使用“if-else”语句会在看似相同的三元运算符构造不会产生 TypeScript 编译器错误?
Posted
技术标签:
【中文标题】为啥使用“if-else”语句会在看似相同的三元运算符构造不会产生 TypeScript 编译器错误?【英文标题】:Why does using an 'if-else' statement produce a TypeScript compiler error when a seemingly identical ternary operator construct does not?为什么使用“if-else”语句会在看似相同的三元运算符构造不会产生 TypeScript 编译器错误? 【发布时间】:2020-08-24 08:57:33 【问题描述】:我有一个函数旨在返回值IDBValidKey
或转换为IDBValidKey
的值。如果我使用三元运算符编写函数,它可以正常工作,但如果我将其编写为 if-else 语句,则会导致编译器错误:
interface IDBValidKeyConvertible<TConverted extends IDBValidKey>
convertToIDBValidKey: () => TConverted;
function isIDBValidKeyConvertible<TConvertedDBValidKey extends IDBValidKey>(object: unknown): object is IDBValidKeyConvertible<TConvertedDBValidKey>
return typeof((object as IDBValidKeyConvertible<TConvertedDBValidKey>).convertToIDBValidKey) === "function";
type IDBValidKeyOrConverted<TKey> = TKey extends IDBValidKeyConvertible<infer TConvertedKey> ? TConvertedKey : TKey;
function getKeyOrConvertedKey<TKey extends IDBValidKey | IDBValidKeyConvertible<any>>(input: TKey): IDBValidKeyOrConverted<TKey>
if (isIDBValidKeyConvertible<IDBValidKeyOrConverted<TKey>>(input))
return input.convertToIDBValidKey();
else
return input;
function getKeyOrConvertedKeyTernary<TKey extends IDBValidKey | IDBValidKeyConvertible<any>>(input: TKey): IDBValidKeyOrConverted<TKey>
return (isIDBValidKeyConvertible<IDBValidKeyOrConverted<TKey>>(input)) ? input.convertToIDBValidKey() : input;
getKeyOrConvertedKeyTernary
不会产生错误,但 getKeyOrConvertedKey
的 else
块会产生此错误:
Type 'TKey' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.
Type 'string | number | Date | ArrayBufferView | ArrayBuffer | IDBArrayKey | IDBValidKeyConvertible<any>' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.
Type 'string' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.
三元运算符和 if-else 语句不是等价的吗?
谢谢!
【问题讨论】:
查看 AST 似乎区别在于ReturnStatement > ConditionalExpression > Binary Expression > ...
与 IfStatement > ((BinaryExpression > ...) + (Block ReturnStatement) + (Block ReturnStatement)
。话虽如此,我不确定为什么会导致这个结果......好问题!
我的直觉是应用类型保护的范围与三元组并不真正兼容。除非 Ryan Cavanaugh 或 Anders Hejlsberg 在发布问题之前阅读此问题,否则很可能是 Github 上某个问题的好人选。
【参考方案1】:
存在两个完全不同的问题:
Typescript 存在错误或限制[1],但它与您的问题所假设的相反。
如果我使用三元运算符编写函数,它可以正常工作
其实,这样不好。 if-else 版本的错误是正确的,三元版本的错误应该是一样的。
您可能做出了这样的假设,因为和我们大多数人一样,我们倾向于假设我们的代码是正确的。这就引出了第二个问题:
-
您的代码不合逻辑。 (请见谅。)
我认为(使用问题中现在简化的代码)#2 应该更容易看到。不过等我有空的时候我会尽量详细解释的。
[1] 这可能不是 Typescript 缺陷,而是由于我不知道的 if-else
和 ? :
之间的一些微妙的不等价而导致的预期行为。我不会感到惊讶,因为 javascript 有很多遗留的怪异之处。 [编辑:见Shaun Luttin's answer,正如我正在输入我的一样。]
【讨论】:
【参考方案2】:为什么使用“if-else”语句会产生 TypeScript 编译器错误,而看似相同的三元运算符构造却没有?
简答
TypeScript 将 if-else
视为包含多个表达式的语句,每个表达式都有独立的类型。 TypeScript 将三元视为一个表达式,它具有真假两面的联合类型。有时,联合类型变得足够宽,编译器不会抱怨。
详细解答
三元运算符和 if-else 语句不是等价的吗?
不完全是。
区别在于三元是一个表达式。有一个conversation here,Ryan Cavanaugh 解释了三元和 if/else 语句之间的区别。值得一提的是,三元表达式的类型是其true
和false
结果的联合。
对于您的特定情况,您的三元表达式的类型是any
。这就是编译器不抱怨的原因。您的三元是 input
类型和 input.convert()
返回类型的联合。在编译时,input
类型扩展了Container<any>
;因此input.convert()
返回类型为any
。因为any
的联合是any
,所以你的三元类型是any
。
您的快速解决方案是将<TKey extends IDBValidKey | IDBValidKeyConvertible<any>
中的any
更改为unknown
。这将使 if-else 和三元都产生编译器错误。
简化示例
这里是a playground link,其中简化了您的问题。尝试将any
更改为unknown
以查看编译器如何响应。
interface Container<TValue>
value: TValue;
declare function hasValue<TResult>(
object: unknown
): object is Container<TResult>;
// Change any to unknown.
const funcIfElse = <T extends Container<any>>(input: T): string =>
if (hasValue<string>(input))
return input.value;
return input;
;
// Change any to unknown.
const funcTernary = <T extends Container<any>>(input: T): string =>
hasValue<string>(input)
? input.value
: input;
【讨论】:
很高兴找到肖恩!我链接到我的答案。我不太明白 Ryan Cavanaugh 关于为什么 Typescript 不能做得更好的解释,但我的头很累:P 感谢您抽出宝贵时间阅读问题并提供解释;它帮助我理解了这个问题!以上是关于为啥使用“if-else”语句会在看似相同的三元运算符构造不会产生 TypeScript 编译器错误?的主要内容,如果未能解决你的问题,请参考以下文章
三元?运算符与 C# 中的传统 If-else 运算符 [重复]
Python:为啥 if-else 一行语句在 else 中不能与 continue 一起使用?