typescript - 为什么 TypeScript 显示无法执行路径的错误?
问题描述
我有这个示例代码:
function func(a?: number, b?: number, c?: number) {
let allGood: boolean = true;
if(!a) {
console.log('a is unacceptable, so do something with a')
allGood = false;
}
if(!b) {
console.log('b is unacceptable, so do something with b')
allGood = false;
}
if(!c) {
console.log('a is unacceptable, so do something with c')
allGood = false;
}
if(allGood) {
console.log(a + b + c) // Error: "Object is possibly undefined" showing for a, b and c
} else {
console.log('oh no!')
}
}
你可以看到,最终只有allGood
当 all且不为假(包括)时才为真。但是在最后一个条件中,TypeScript 抱怨每个,和可能是未定义的。在不重新检查真实性的情况下解决此问题的最佳方法是什么,并且因为在此条件之前已经检查过它们。a
b
c
undefined
a
b
c
a
b
c
解决方案
简短的回答:控制流缩小本质上是启发式的,不考虑变量之间的这种相关性。microsoft/TypeScript#20497存在一个未解决的问题,请求支持所谓的“分支标志”,例如您的allGood
. 但实施起来可能太复杂了。
长版:
TypeScript 使用控制流分析来缩小代码块中变量和属性的明显类型,在这些类型保护或赋值发生后,它可以跟踪代码块。因此,例如,在以下示例代码中,编译器可以看到真实性检查a
会将其范围从 缩小number | undefined
到number
:
if (!a) { } else {
a + 1; // okay
}
但是在语句的真假分支if
都完成后,如果来自多个路径的控制流再次连接起来,编译器会将缩小的类型重新连接到每个分支的任何缩小的联合中:
let x: number | string | undefined = Math.random() < 0.5 ? 123 : undefined;
// x is number | undefined
if (typeof x === "undefined") {
// x is undefined
x = "hello"; // x is string
} else {
x += 789; // x is number
}
// x is string | number
x.valueOf(); // okay
但请注意,这些变窄/变宽通常针对不同的变量独立发生:
let someBoolean: boolean;
let x: number | string | undefined = Math.random() < 0.5 ? 123 : undefined;
// x is number | undefined
if (typeof x === "undefined") {
// x is undefined
x = "hello"; // x is string
someBoolean = true;
} else {
x += 789; // x is number
someBoolean = false;
}
// x is string | number
x.valueOf(); // okay
someBoolean; // boolean
在此块的末尾,someBoolean
is boolean
(相当于 union true | false
)和x
is string | number
。现在事实证明someBoolean
和x
是相互关联的;someBoolean
不能是true
whilex
是 a number
,someBoolean
也不能是false
whilex
是 a string
。但是编译器不会跟踪这种相关性。它将它们视为不相关或独立的联合类型,因此假设这种不可能的情况是可能的。
我打开了microsoft/TypeScript#30581来强调这是人们遇到的一个痛点,但是没有明显的解决方案可以工作。
为了自动和一般地跟踪相关性,编译器必须开始进行计算,以模拟它考虑的每个联合类型变量或属性的每一种可能的缩小。对于每一个额外的变量或属性,这会使编译器的工作量乘以某个因子。因此编译时间会在变量和属性的数量上呈指数增长。所以这不可能发生。
正如封闭的microsoft/TypeScript#25051中所建议的那样,可以想象一种要求编译器考虑手动指定的变量或属性集的方法,以便您只在对您而言值得时付出性能损失。该建议因多种原因而被关闭,包括将函数体包装在类似type switch (a, b, c) { ... }
.
并且在microsoft/TypeScript#20497有一个特定的请求,以支持allGood
跟踪特定条件的“分支标志” 。但同样,没有明显的方法来实现它并保持合理的编译器性能。
结语:
您将不得不解决这个问题;通过使用类型断言a
告诉编译器您对,b
和的类型了解c
得比它多:
if (allGood) {
console.log(a! + b! + c!) // assertions
} else {
console.log('oh no!')
}
(这里我使用了非空断言操作符!
作为更简洁的版本(a as number) + (b as number) + (c as number)
)
或者通过重构为不依赖于此类相关变量的版本,例如(诚然奇怪):
function func(a?: number, b?: number, c?: number) {
let good = (a: number) => (b: number) => (c: number) => () => console.log(a + b + c);
let aGood;
if (!a) {
console.log('a is unacceptable, so do something with a')
} else {
aGood = good?.(a);
}
let abGood;
if (!b) {
console.log('b is unacceptable, so do something with b')
} else {
abGood = aGood?.(b)
}
let abcGood;
if (!c) {
console.log('c is unacceptable, so do something with c')
} else {
abcGood = abGood?.(c)
}
if (abcGood) {
abcGood();
} else {
console.log('oh no!')
}
}
func(1, 2, 3) // 6;
func(1) // b unacceptable, c unacceptable, oh no!
显然,在这种情况下,断言不那么突兀,所以这就是我的建议。
推荐阅读
- asp.net - 错误 403 - 禁止访问:访问被拒绝。ASP.NET
- python - 使用 cpu 查找前 5 个进程的 Python 代码
- angular - 条件子路由(Angular)
- javascript - 通过更改一些列在 Laravel 中复制 Eloquent
- excel - 如何检测带有公式的单元格是否为空白?
- firebase - 侧边菜单中的当前用户 Ionic
- csv - 试图将一条恒定斜率线拟合到两个特定范围的数据点
- spring-data-jpa - 如何从自动配置类添加 ImplicitNamingStrategy
- sql - 我们可以在使用连接时在另一个 SQL 查询中插入一个 SQL 查询吗?
- amazon-web-services - 如何将现有的 AWS WAF ACL 导入 terraform?