我在操场上有下面的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}
的Foo
s。
游乐场链接到代码
为了使Typescript缩小else
语句中的类型,该类型必须是Union类型。由于Foo
不是Union类型,Typescript没有可用于将foo
缩小为.的类型
TS回购中存在相关问题https://github.com/microsoft/TypeScript/issues/36887
要在用例中利用typeguard,您可以只检查属性类型,因为它是string | number
并集,也可以使用并集类型对Foo
进行建模。