Here is a minimal example of my problem
interface Dog {
legs: number;
}
interface Person {
arms: number;
}
type Living = Person | Dog;
interface Speaker<T extends Living> {
speak: (living: T) => void;
};
const michel: Speaker<Person> = { speak: (person) => console.log(`I have ${person.arms} arms`) };
const speakers: Array<Speaker<Living>> = [michel];
It throw this error
Type 'Speaker<Person>' is not assignable to type 'Speaker<Living>'.
Type 'Living' is not assignable to type 'Person'.
Property 'harms' is missing in type 'Dog' but required in type 'Person'
I would like to have an array of Speaker
that accepts any type of Living
.
Thanks for your time!
CodePudding user response:
With the way you've defined things, Speaker<Living>
means an object with a speak function that takes in a Person | Dog
as its argument. When you create michel
, you have the following function, which works fine if given a Person
:
(person) => console.log(`I have ${person.arms} arms`)
But it would not work if given a Person | Dog
, because it's trying to access the .arms
property of a dog. That mismatch and others is why typescript is telling you that Speaker<Person>
cannot be assigned to Speaker<Living>
If you want the array to be a mix of objects, some of which take Person
s for their speak function, some of which take Dog
s, then the type for that will be:
const speakers: Array<Speaker<Person> | Speaker<Dog>> = [michel];
EDIT: if your Living
union is large, or you want speakers to update automatically when more types are added into Living
, then you can use the following to get typescript to generate the Speaker<Person> | Speaker<Dog>
union for you:
type Distribute<T> = T extends Living ? Speaker<T> : never;
const speakers: Array<Distribute<Living>> = [michel];
// speakers is of type Array<Speaker<Person> | Speaker<Dog>>