Home > Net >  How can I discriminate a union of interfaces using an object map without switch statement?
How can I discriminate a union of interfaces using an object map without switch statement?

Time:06-28

I'm fairly new to advanced typescripts concepts so bear with me. I have a number of projects that use objects to map out react components by some common prop (e.g _type).

What I want to do is automatically return the correct type for the component resolved using these object maps, however I just receive a type of any when I try this.

Many posts online recommend using switch statement (example below). Our company uses the object map pattern so I'd like to avoid using the switch statements if possible. Thanks. For added context, this is our first react project using typescript (moving from javascript).

// types
type AnimalType = 'cat' | 'dog'

interface Cat {
  _type: AnimalType
  name: string
  onMeow: () => void
}

interface Dog {
  _type: AnimalType
  name: string
  onBark: () => void
}
type Animal = Cat | Dog

type AnimalMap = {
  [property in Animal['_type']]
}
// THIS WORKS BUT WE DON'T WANT TO USE THIS
// resolving animal type via switch
const CatComponent = ({ _type, name, onMeow }: Cat) => <div>I'm a cat!</div>

const DogComponent = ({ _type, name, onBark }: Dog) => <div>I'm a dog!</div>

function resolveAnimal(_type: AnimalType): React.Component<Animal> {
  
  switch(_type) {
    case "dog": return DogComponent
    case "cat": return CatComponent
  }
  return animalMap[_type]
}

// THIS DOESN'T WORK, WE'D LOVE TO USE THIS!
// resolving animal type via object map

const animalMap: AnimalMap = {
  cat: CatComponent,
  dog: DogComponent,
}

function resolveAnimal(_type: AnimalType): React.Component<Animal> {
  return animalMap[_type]
}

function App() {
  const ResolvedAnimalComponent = resolveAnimal('dog')
  return <ResolvedAnimalComponent onBark={() => console.log('HE BARKED')} />
}

CodePudding user response:

Remove the explicit notation of : AnimalMap so that the object declaration values are not widened - you want TypeScript to see

const animalMap: {
  cat: CatComponent,
  dog: DogComponent,
}

not

const animalMap: AnimalMap

(not only because your AnimalMap doesn't note the values - the components - but also because it doesn't tie the properties to their components)

Use

const animalMap = {
    cat: CatComponent,
    dog: DogComponent,
}

function resolveAnimal<T extends keyof typeof animalMap>(_type: T) {
    return animalMap[_type]
}
  • Related