Reputation: 23873
Say, I have a class like this, and TypeScript is not happy about it:
class Foo extends ThirdPartyLibrary {
animal!: Animal;
bar(): void {
this.animal.quack(); // Method `quack` does not exist on type `Animal` 😱
}
}
The official way to resolve this seems to be using an ad-hoc type narrowing function:
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();
}
}
In certain cases, such runtime checks are an overkill. For example, when using a poorly typed third-party library, I don't want to test the library in runtime and write all that boilerplate code.
Instead, I want to write it as short as possible.
My best attempt so far is to reassign the variable to another variable with as
type casting:
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
}
}
But I don't need that variable! I only need TypeScript to know the type statically and I don't want to declare any runtime variables or if-clauses or errors thrown.
I'm imaginging something like this:
class Foo extends ThirdPartyLibrary {
animal!: Animal;
bar(): void {
this.animal is Duck; // Narrow down the type without boilerplate
this.animal.quack();
}
}
I just want TypeScript to statially know that at this point in code this variable/property is certain to be of a specific type. How do I do that?
Solutions that do not work for me:
Type the animal
property to be a Duck
:
class Foo extends ThirdPartyLibrary {
animal!: Duck;
}
This solution is the most rational for my simple example, but that's just a minimal artifical example I came up with.
Imagine that the Foo
class is more complicated than that: this.anmial
is used many times, and most of the time it can be any Animal
. Only at this specific point in code it's known for certain to be a duck.
Use inline type casting: (this.animal as unknown as Duck).quack()
.
This works, but when you need to do more than one ducky thing with this animal, this approach becomes annoying:
(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!");
Fixing the typings of the third-party library. Assume that the typings are really compliated and you don't have the capacity to dig into them.
Upvotes: 0
Views: 124
Reputation: 916
You can define a phantom narrowing predicate function that doesn't have as much runtime cost:
function narrow<U>(x: any) : x is U { return true }
...
bar(): void {
if (narrow<Duck>(this.animal))
this.animal.quack();
}
But I don't think this is close enough to your best expectation.
Upvotes: 1