When using arrays with different Types in typescript I tend to run into issues with properties that are not on all Types.
I ran into the same issue for different types of sections on a page, different roles of users with different properties and so on.
Here is an example with animals:
For example, if you have types Cat, Dog and Wolf:
export type Cat = {
animal: 'CAT';
legs: 4;
}
export type Dog = {
animal: 'DOG',
legs: 4,
requiresWalks: true,
walkDistancePerDayKm: 5
}
export type Wolf = {
animal: 'WOLF',
legs: 4,
requiresWalks: true,
walkDistancePerDayKm: 20
}
type Animal = Cat | Dog | Wolf;
const animals: Animal[] = getAnimals();
animals.forEach(animal => {
// here i want to check if the animal requires a walk
if (animal.requiresWalks) {
// Property 'requiresWalks' does not exist on type 'Animal'. Property 'requiresWalks' does not exist on type 'Cat'.
goForAWalkWith(animal)
}
});
// The type "AnimalThatRequiresWalks" does not exist and i want to know how to implement it
goForAWalkWith(animal: AnimalThatRequiresWalks) {
}
As commented above, property requiresWalks can not be used to narrow down the type.
Also imagine we have 20 animals. I am having difficulties implementing types that may extend animals like "AnimalThatRequiresWalks" which may have multiple properties related to walking animals.
What is a clean implementation to join these types with a type "AnimalThatRequiresWalks" (with properties "requiresWalks true" and "walkDistancePerDayKm") and how can i properly narrow it down to "AnimalThatRequiresWalks"?
CodePudding user response:
You have two questions:
How do you check
requiresWalks
to see if the animal requires a walk?How do you define the type for
animal
ingoForAWalkWith
?
Re #1: You test to see if the object has the property before you try to use it (this is a specific type of narrowing the handbook calls in
operator narrowing):
animals.forEach(animal => {
if ("requiresWalks" in animal && animal.requiresWalks) {
// −−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
goForAWalkWith(animal)
}
});
Re #2, you can extract from the Animal
union all types that are assignable to {requiresWalks: true}
via the Extract
utility type:
function goForAWalkWith(animal: Extract<Animal, {requiresWalks: true}>) {
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ...
}
Extract<Animal, {requiresWalks: true}>
is Dog | Wolf
.
You con't have to do that inline if you don't want to, you can define a type alias for it, then use the alias:
type AnimalThatRequiresWalks = Extract<Animal, {requiresWalks: true}>;
// ...
function goForAWalkWith(animal: AnimalThatRequiresWalks) {
// ...
}