Home > Mobile >  typescript: Array of templated interface extending multiple types
typescript: Array of templated interface extending multiple types

Time:11-09

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 Persons for their speak function, some of which take Dogs, 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>>

Playground link

  • Related