这是目前 TypeScript 的一个限制,如中所述微软/TypeScript#10530.
你期望通过检查feeObj[property] !== undefined
TypeScript 将narrow的类型feeObj[property]
from number | undefined
to undefined
,但不幸的是这并没有发生。 TypeScript 通常允许这样的平等缩小关于属性访问,如下所示:
function works(feeObj: FeeObjType2) {
const property = "someProp";
feeObj[property] + 2; // error, possibly undefined
if (feeObj[property] !== undefined) {
feeObj[property] + 2; // okay
}
}
这有效是因为property
有字符串文字类型 of "someProp"
。编译器知道该键的确切字面值。所以在你检查之后"someProp"
的属性键feeObj
for undefined
,编译器会意识到它不能undefined
在后续代码块中,并允许您将其视为number
.
那么为什么下面的方法不起作用呢?
function doesNotWork(feeObj: FeeObjType2, property: string) {
// (parameter) property: string
feeObj[property] + 2; // error, possibly undefined
if (feeObj[property] !== undefined) {
feeObj[property] + 2; // still error, possibly undefined!
}
}
这是因为类型property
is string
,即not文字类型,目前缩小行为仅适用于types属性键,而不是它们的身份。从类型系统的角度来看,上面的代码等同于:
function alsoBad(feeObj: FeeObjType2, property1: string, property2: string) {
if (feeObj[property1] !== undefined) {
feeObj[property2] + 2; // error of course
}
}
在这里你正在检查feeObj[property1]
然后访问feeObj[property2]
。这是有道理的,这不安全,对吧?您可能会想“这当然不安全,因为property1
and property2
可能不具有相同的值,而property
显然具有相同的价值itself。”但是编译器没有跟踪键的身份,因此它不知道比较是与“自身”进行的。
进行同样的练习property1
and property2
两者具有相同的文字类型"someProp"
,您会看到它再次开始工作:
function alsoGood(feeObj: FeeObjType2) {
const property1 = "someProp";
// const property1: "someProp"
const property2 = "someProp";
// const property2: "someProp"
if (feeObj[property1] !== undefined) {
feeObj[property2] + 2; // okay
}
}
因此,目前,通过检查对象属性来缩小对象类型的唯一方法是检查已知文字类型的属性。
在这种情况下,建议的解决方法是在检查属性之前将其复制到新变量。这样你总是缩小同一个变量的范围:
const sumFunc = function (feeObj: FeeObjType2): number {
let total = 0;
for (const property in feeObj) {
if (property) {
const fp = feeObj[property]; // copy
if (fp !== undefined) { // check
total = total + fp; // okay
}
}
}
return total;
};
当然,在特定的示例中,您不需要如此冗长:没有真正的理由要检查property
为了真实(如果有人写feeObj2[""] = 123
, 你会really想要将其从总和中排除吗?);你可以替换x = x +
与加法赋值运算符 (+=);您可以使用零值合并 (??) 操作员处理undefined
而不是有一个单独的代码块。这给你:
const sumFunc = function (feeObj: FeeObjType2): number {
let total = 0;
for (const property in feeObj) {
total += feeObj[property] ?? 0;
}
return total;
};
其行为应该类似,而不需要依赖于缩小范围feeObj[property]
at all.
Playground 代码链接