类中用户定义的类型保护的求值为false



我在操场上有下面的Typescript。

这是代码:

class Foo {
public get test() : string|number{
return "foo"
}
public  hasString() : this is { test:string }{
return typeof this.test === "string";
}
}
const foo = new Foo();
if(!foo.hasString()) {
console.log(foo.test - 10); // <-- this is an error
}
if(typeof foo.test == "number") {
console.log(foo.test - 10);
}

据我所知,当hasString()为false时,它唯一可以是的其他值是number。然而,typescript仍然给出一个错误:

算术运算的左侧必须是"any"类型,"number"、"bigint"或枚举类型。

为什么typescript编译器不够聪明,无法实现如果不是字符串,那么它必须是一个数字。

false的情况下,不能使用用户定义的类型保护来缩小非联合类型对象的类型。一般来说,类型保护的false情况的主要问题是,TypeScript不支持在microsoft/TypeScript#29137中实现(但从未合并(的排序的任意否定类型,其中not X将只匹配类型为X而非的所有值。

如果你有一个像A | B | C | D这样的并集类型,那么很容易说某个类型的保护应该"分割";对于成功的测试,键入类似A | B的内容,或者对于失败的测试键入C | D的内容。毕竟,如果您有一个类型为A | B | C | D的值,并且您知道它不是A | B,那么它<em]必须是>C | D。但如果你有一个非联合类型,那么就没有什么可分割的了。如果您有一个类型为X的值,并且您知道它Y,那么您可以将其缩小到交集X & Y。但如果它不是Y,就没有像X & not Y这样的类型可以将其缩小到;编译器所能做的最好的事情就是将其保留为CCD_ 18。因此,非联合类型对象的类型保护本质上是不对称的。

通过使hasString()成为this上的类型保护,编译器可以在true的情况下将值从Foo缩小到Foo & {test: string}。但在false的情况下,没有Foo & not {test: string}。编译器将其保留为Foo

你可以在microsoft/TypeScript#36687 中阅读更多关于这种情况的信息


如果Foo等效于{test: string} | {test: number},则类型保护将按照需要进行操作。但是类型检查器并不认为{test: string | number}等同于该类型。一般来说,编译器在属性外传播并集的成本会高得令人望而却步;有关详细信息,请参阅microsoft/TypeScript#12052。

可以尝试以这种方式重构Foo,但class声明不能生成并集类型,因此您需要做其他事情,可能是这样的:

interface FooBase {
test: string | number;
hasString(): this is FooString
}
interface FooString extends FooBase {
test: string;
}
interface FooNumber extends FooBase {
test: number;
}
type Foo = FooString | FooNumber;
const fooMaker = () => ({
get test(): string | number {
return "foo"
},
hasString() {
return typeof this.test === "string";
}
}) as Foo;
const foo = fooMaker();
if (!foo.hasString()) {
console.log(foo.test - 10); // <-- okay
}

这很管用,但很恶心。


您的示例代码似乎只关心test属性。如果是这样,您可能只想对test属性本身执行类型保护,而不是对foo执行类型保护。Foo可能不是并集,但test属性是并集。我们知道,即使在false的情况下,编译器也可以过滤这些。事实上,这就是您在示例中所做的:

class Foo {
public get test(): string | number {
return "foo"
}
}
const foo = new Foo();
if (typeof foo.test !== "string") {
console.log(foo.test - 10); // <-- okay
}

如果你真的想要一个用户定义的类型保护,你可以写一个:

const isString = (x: any): x is string =>
typeof x === "string";
if (!isString(foo.test)) {
console.log(foo.test - 10); // <-- okay
}

但由于CCD_ 38已经起到了类型保护的作用。


因此,我的建议只是直接对test属性进行typeof检查,直到在该语言中引入否定类型,这样它就可以更好地推断不是{test: strings}Foos。

游乐场链接到代码

为了使Typescript缩小else语句中的类型,该类型必须是Union类型。由于Foo不是Union类型,Typescript没有可用于将foo缩小为.的类型

TS回购中存在相关问题https://github.com/microsoft/TypeScript/issues/36887

要在用例中利用typeguard,您可以只检查属性类型,因为它是string | number并集,也可以使用并集类型对Foo进行建模。

最新更新