为什么TypeScript显示不可执行路径的错误?

为什么TypeScript显示不可执行路径的错误?,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 som

我有以下示例代码:

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!')
  }
}

您可以看到,只有当所有
a
b
c
都不正确(包括
未定义的
)时,最后的
allGood
才是正确的。但在最后一个条件中,TypeScript抱怨
a
b
c
中的每一个都可能未定义。在不重新检查
a
b
c
的真实性的情况下,解决这个问题的最佳方法是什么?因为它们在这种情况之前已经被检查过了。

简单回答:控制流缩小本质上是启发式的,并且不考虑变量之间的这种相关性。在请求支持所谓的“分支标志”(如
allGood
)时存在一个公开问题。但这是为了实施


长版本:

TypeScript用于缩小代码块中变量和属性的明显类型,在代码块中,它可以遵循已发生的类型保护或赋值。因此,例如,在下面的示例代码中,编译器可以看到对
a
的真实性检查将把它从
number | undefined
缩小到
number

if (!a) { } else {
  a + 1; // okay
}
但是在
if
语句的true和false分支完成后,如果来自多个路径的控制流再次合并,编译器将把缩小的类型重新合并到来自每个分支的任何缩小类型中:

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
(在这里,我使用了更简洁的
(a作为数字)+(b作为数字)+(c作为数字)

或者通过重构到一个不依赖于这些相关变量的版本,例如(无可否认是奇怪的):

显然,在本例中,断言不那么突出,因此这将是我的建议


对于类型系统来说,这是一个相对复杂的问题。虽然简单的推理“显而易见”,但它涉及到观察对单独变量的跟踪变化(类似于执行程序…)。如果(!a&&!b&&!c)
,询问和回答
会容易得多。我可能会重写此代码以消除
allGood
变量,并将失败的检查和特定处理移到else分支;或者对无效案例使用范围中断构造(
return
),这将导致预期的TS行为。什么版本的Typescript?我正在复制,没有错误。
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
if (allGood) {
  console.log(a! + b! + c!) // assertions
} else {
  console.log('oh no!')
}
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!