2

给定代码

class X {a:string = "a"}
class Y {b:number = 1}

// does not compile
function what(v: X|Y) {
  if (v instanceof X) {
    return "its an X";
  }
  if (v instanceof Y) {
    return "its a Y";
  }
  //missing return
}

打字稿编译器说:

error TS7030: Not all code paths return a value.

没错,因为由于结构类型的原因,我可以what称之为

let v = {b:1}
what(v)

从那时起v在结构上与Y. 现在我变成what

function what(v: X|Y) {
  if ("a" in v) {
    return "its an X";
  }
  if ("b" in v) {
    return "its a Y";
  }
  // missing return
}

并且仍然得到编译错误。

我想知道编译器是否无法导出将采用其中一个分支,或者是否存在仍然允许我传入与两个分支if中的任何一个都不匹配的兼容对象的漏洞。if

4

2 回答 2

2

这是 TypeScript 的设计限制。您可以合理地期望详尽检查将使编译器意识到最后一条if语句之后的代码无法访问。但这不会发生,而且看起来他们也不打算很快解决这个问题。

让我们看看我们自己能做些什么。在以下代码中:

class X { a: string = "a" }
class Y { b: number = 1 }

// does not compile
function what(v: X | Y): string {
  if (v instanceof X) {
    return "its an X";
  }
  v // Y
  if (v instanceof Y) {
    return "its a Y";
  }
  v // never
}

v当您传递这些语句时,编译器实际上确实缩小了的类型......它会抱怨,但不是因为当您从函数底部掉下来时return它认为v可能仍然是 aX或 a 。Y它抱怨是因为它分析代码路径与缩小的隔离v,所以它看到一个undefined在最后隐式返回的代码路径。解决此问题的唯一方法是确保在所有代码路径中都有一个return或。throw

再次注意,v首先缩小到Y,然后缩小到never。修复错误的一种方法是利用缩小到Y,并在那里做一个return而不是第二次if测试:

function what(v: X | Y): string {
  if (v instanceof X) {
    return "its an X";
  }
  v // Y
  return "its a Y";
}

现在没有错误了。如果你相信你的instanceof X支票,这可能是要走的路。另一种标准方法是使用详尽检查辅助函数assertNever()

function assertNever(x: never): never {
  throw new Error("OH NOES!!");
}

如果你调用它,该assertNever()函数会引发错误,因此它会返回never. 但它要求它的参数是 type never,所以调用它应该是编译器错误,除非代码不可访问:

function what(v: X | Y): string {
  if (v instanceof X) {
    return "its an X";
  }
  v // Y
  if (v instanceof Y) {
    return "its a Y";
  }
  v // never
  return assertNever(v);
}

现在what()已知返回string | never而不是string | undefined. 而且string | never只是string。你可以看到它是如何作为一个详尽检查的,因为如果你不缩小v到它会抛出一个错误never

function oops(v: X | Y): string {
  if (v instanceof X) {
    return "its an X";
  }
  return assertNever(v); // error, v is Y, not never
}

好的,希望这些帮助。祝你好运!

于 2019-03-16T18:53:42.440 回答
1

我知道使用接口和标记联合的不同版本。并且此版本正确编译,所有代码路径都返回一个值。

interface IX {kind: "a"; a: string }
interface IY {kind: "b"; b: number }

function what2(v: IX|IY): string {
    switch (v.kind) { 
        case "a":
         return "its an X";
         case "b":
         return "its an Y";
    }
}

var r1 = what2({kind: "a", a: "hello"});
var r2 = what2({kind: "b", b: 33});
于 2019-03-16T14:50:55.153 回答