比如说,我有一个这样的类,TypeScript不喜欢它:
class Foo extends ThirdPartyLibrary {
animal!: Animal;
bar(): void {
this.animal.quack(); // Method `quack` does not exist on type `Animal` 😱
}
}
解决这个问题的官方方法似乎是使用一个特别的类型缩小函数:
class Foo extends ThirdPartyLibrary {
animal!: Animal;
bar(): void {
function isDuck(maybeDuck: unknown): maybeDuck is Duck {
return maybeDuck?.look === 'ducky' && maybeDuck?.voice === 'quacky';
}
if (!isDuck(this.animal)) {
throw new Error('Expected argument to be a duck');
}
this.animal.quack();
}
}
在某些情况下,这样的运行时检查是多余的。例如,当使用一个类型很差的第三方库时,我不想在运行时测试库并编写所有的样板代码。
相反,我想把它写得尽可能短。
到目前为止,我最好的尝试是使用as
类型强制转换将变量重新分配给另一个变量:
class Foo extends ThirdPartyLibrary {
animal!: Animal;
bar(): void {
const animal = this.animal as unknown as Duck;
animal.quack(); // Now TypeScript knows that this animal can quack
}
}
但是我不需要这个变量!我只需要TypeScript静态地知道类型,我不想声明任何运行时变量或if子句或抛出错误。
我想象的是这样的:
class Foo extends ThirdPartyLibrary {
animal!: Animal;
bar(): void {
this.animal is Duck; // Narrow down the type without boilerplate
this.animal.quack();
}
}
我只是想让TypeScript静态地知道,在代码的这一点上,这个变量/属性肯定是一个特定的类型。我怎么做呢?
不适合我的解决方案:
输入
animal
属性为Duck
:class Foo extends ThirdPartyLibrary { animal!: Duck; }
这个解决方案对于我的简单例子来说是最合理的,但那只是我想出的一个最小的人工例子。
想象
Foo
类比这更复杂:this.anmial
被使用了很多次,大多数时候它可以是任何Animal
。只有在代码的这个特定点,它才被确定为鸭子。使用内嵌类型转换:
(this.animal as unknown as Duck).quack()
.这个方法有效,但是当你需要对这只动物做不止一件事情时,这种方法就变得很烦人了:
(this.animal as unknown as Duck).quack(); (this.animal as unknown as Duck).waddle(); (this.animal as unknown as Duck).awwYiss("Motha. Funkin. Bread crumbs!");
修复第三方库的类型。假设类型非常复杂,您没有能力深入研究它们。
您可以定义一个不需要太多运行时成本的虚缩谓词函数:
function narrow<U>(x: any) : x is U { return true }
...
bar(): void {
if (narrow<Duck>(this.animal))
this.animal.quack();
}
但我不认为这是足够接近你的最佳期望。