TypeScript-防止null的类型防护

bse*_*ula 5 typescript

我已经在null对象防护中使用了下面的防护类型,但是仍然出现错误:

function a(par: {a: string; b: null | string}): {a: string; b: string} | undefined {
  if (par.b === null) {
    return;
  }

  return par;
}
Run Code Online (Sandbox Code Playgroud)

输入'{a:string; b:字符串| 空值; }'不能分配给类型'{a:string; b:字符串;}'。属性“ b”的类型不兼容。输入'string | “ null”不可分配给“ string”类型。类型“ null”不可分配给类型“ string”。

我认为如果我进行检查par.b === null,TS应该推断不可能返回有的对象prop.b === null

还是我在这里感到困惑?

jca*_*alz 6

TL;DR:您并不疯狂,但您对编译器的期望超出了它所能提供的。使用类型断言并继续。


TypeScript 并不倾向于确定类型保护检查的所有可能含义。检查对象的属性会缩小对象本身类型的少数几个地方之一是当对象是判别联合并且您正在检查的属性是判别属性时。在您的情况下,par它本身甚至不是联合类型,更不用说歧视联合了。因此,当您检查par.b编译器确实缩小par.b但不会将缩小向上传播为par自身缩小。

可以做到这一点,但问题是这样的计算对于编译器来说很容易变得昂贵,正如一位语言架构师此评论中所描述的:

似乎对于x控制流图中的每个引用,我们现在必须检查每个x以虚线名称作为基本名称的类型保护。换句话说,为了知道 的类型,x我们必须查看 的属性的所有类型保护x。这有可能产生大量工作。

如果编译器和人类一样聪明,它可能只在这些额外检查可能有用时才执行这些检查。或者,也许聪明的人可以写出一些足够适用于这个用例的启发式方法;但我想在实践中,这在任何人进入该语言的优先级列表中都不是很高。我还没有在 GitHub中发现一个表明这一点的未决问题,所以如果你对此有强烈的感觉,你可能想提交一个。但我不知道它会受到多大的欢迎。

在没有更聪明的编译器的情况下,有一些解决方法:


最简单的解决方法是接受你比编译器更聪明的事实,并使用类型断言告诉它你确信你正在做的事情是安全的,它不应该太担心验证它。类型断言一般来说有点危险,因为如果您使用一个断言并且断言是错误的,那么您只是对编译器撒了谎,由此产生的任何运行时问题都是您的错。但在这种情况下,我们可以非常自信:

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 
}
Run Code Online (Sandbox Code Playgroud)

这可能是这里的方法,因为它使您的代码保持基本相同并且断言非常温和。


另一种可能的解决方法是使用用户定义的类型保护函数来缩小par自身范围。这有点棘手,因为不对联合类型起作用的类型保护函数不会在 " else" 分支中缩小范围……可能是因为该语言缺少否定类型。也就是说,如果你有一个 type guardfunction guard(x: A): x is A & B和 call if (guard(x)) { /*then branch*/ } else { /*else branch*/ }x将被缩小到A & B“then”分支内,但只会A在“ else”分支中。没有A & not B可以使用的类型。你能得到的最接近的是 do 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;
}
Run Code Online (Sandbox Code Playgroud)

propNotNull(obj, key)后卫会,如果返回true,缩小obj到其中类型obj.key是已知不是null(或undefined...只是因为NonNullable<T>是一个标准的实用型)。

现在你的a()函数可以写成:

function aUserDefinedTypeGuard(par: {
  a: string;
  b: null | string;
}): { a: string; b: string } | undefined {
  if (!propNotNull(par, "b")) {
    return;
  } else {
    return par;
  }
}
Run Code Online (Sandbox Code Playgroud)

检查!propNotNull(par, "b")原因par没有在所有在第一分支缩小,但是变窄par{a: string; b: string}第二分支。这足以使您的代码编译器没有错误。

但我不知道与类型断言相比是否值得额外的复杂性。


好的,希望有帮助;祝你好运!

代码链接