我在null
对象道具中使用了下面的防护类型,但仍然出现错误:
function a(par: {a: string; b: null | string}): {a: string; b: string} | undefined {
if (par.b === null) {
return;
}
return par;
}
输入'{a:string; b:字符串|空值; }'不能分配给类型'{ a:字符串; b:字符串; }'。属性“ b”的类型不兼容。 输入'string | “ null”不可分配给“ string”类型。 不能将'null'类型分配给'string'类型。
我认为,如果我检查par.b === null
,TS应该推断不可能返回具有prop.b === null
的对象。
还是在这里让我感到困惑?
答案 0 :(得分:4)
TL; DR:您并不疯狂,但是您期望编译器提供的功能超出了预期。使用类型断言并继续。
TypeScript不能确定类型保护检查的所有可能含义。当检查对象的属性为discriminated union且要检查的属性为判别属性时,检查对象的属性会缩小对象本身类型的少数地方之一。在您的情况下,par
本身甚至不是联合体类型,更不用说是受歧视的联合体了。因此,当您检查par.b
时,编译器会缩小par.b
的范围,但不会将该缩小范围向上传播为par
的范围。
可以做到这一点,但是问题在于,这种计算对于编译器而言很容易变得昂贵,如this comment by one of the language architects中所述:
对于控制流程图中的每个对
x
的引用,我们现在必须检查每个以点名命名为x
作为基本名称的类型防护。换句话说,为了知道x
的类型,我们必须查看x
属性的所有类型防护。那有可能产生很多工作。
如果编译器像人一样聪明,则只有在可能有用时,才可能执行这些额外的检查。或者,一个聪明的人可以写出一些启发式的方法,对这个用例来说已经足够了。但是我认为实际上,进入该语言的优先级并不高。我没有找到建议这样做的open issue in GitHub,因此,如果您对此有强烈的兴趣,则可能要提出一个。但是我不知道它会收到多好。
在没有更聪明的编译器的情况下,有一些解决方法:
最简单的解决方法是接受您比编译器更聪明的信息,并使用type assertion来告诉您您确定自己在做的事是安全的,并且不必为验证它担心太多。通常,类型断言有些危险,因为如果您使用一个类型断言,并且断言是错误的,那么您只会对编译器撒谎,而由此引起的任何运行时问题都是您的错。但是在这种情况下,我们可以很自信:
function aAssert(par: {
a: string;
b: null | string;
}): { a: string; b: string } | undefined {
if (par.b === null) {
return;
}
return par as { a: string; b: string }; // I'm smarter than the compiler ?
}
这可能是解决问题的方法,因为它可以使您的代码保持基本相同,并且断言相当温和。
另一种可能的解决方法是使用user-defined type guard函数来缩小par
的范围。这有点棘手,因为对联合类型不起作用的类型保护函数不会在“ else
”分支中缩小范围……可能是因为该语言缺少negated types。也就是说,如果您有类型防护function guard(x: A): x is A & B
,并且调用if (guard(x)) { /*then branch*/ } else { /*else branch*/ }
,则x
在“ then”分支内将缩小为A & B
,而只是{{ 1}}在“ A
”分支中。没有else
类型可以使用。您能获得的最接近的结果是进行A & not B
,但这只是切换哪个分支变窄。
所以我们可以这样做:
if (!guard(x)) {} else {}
function propNotNull<T, K extends keyof T>(
t: T,
k: K
): t is { [P in keyof T]: P extends K ? NonNullable<T[P]> : T[P] } {
return t[k] != null;
}
后卫将在返回propNotNull(obj, key)
的情况下,将true
缩小为已知obj
不是obj.key
(或{ {1}} ...仅因为NonNullable<T>
是标准实用程序类型)。
现在您的null
函数可以写为:
undefined
支票a()
导致function aUserDefinedTypeGuard(par: {
a: string;
b: null | string;
}): { a: string; b: string } | undefined {
if (!propNotNull(par, "b")) {
return;
} else {
return par;
}
}
在该第一个分支中根本不缩小,而在第二个分支中将!propNotNull(par, "b")
缩小为par
。这足以使您的代码编译器没有错误。
但是我不知道与类型断言相比是否值得额外的复杂性。
好的,希望能有所帮助;祝你好运!
答案 1 :(得分:0)
如果我不介意,如果对象par为null,则par.b是错误