Andrew Allison
Andrew Allison

Reputation: 1136

Typescript: Exclude one type from union that is extended by other types in the union

I have a union type like this:

export type TileType =
      | ProductTileType
      | CustomTileType
      | DealTileType
      | LoadingTileType

And a React component that needs to be able to take a prop of type: ProductTileType | CustomTileType | DealTileType. So I declared that prop with type Exclude<TileType, LoadingTileType> so as not to allow that type to be used here. The only problem is that ProductTileType is declared like this:

export interface ProductTileType extends LoadingTileType { ... }

When I exclude LoadingTileType TS also wants to exclude any type that extends it. How can I avoid excluding types that extend what I want to exclude? Is there another utility type I am not aware of?

Edit:

type MyType = Exclude<TileType, LoadingTileType> | ProductTileType would be a quick workaround to solve the problem at hand (In this case ProductTileType was the only one extending LoadingTileType) But in the future there might be others types that extend LoadingTileType. What would be a better way to handle that?

Upvotes: 4

Views: 2210

Answers (1)

tenshi
tenshi

Reputation: 26324

Let's say we have some drinks:

interface Water { healthiness: 1000 }
interface Lemonade { healthiness: 500 }
interface Tea { healthiness: 1250 } // really it depends on what kind of tea

interface Soda { healthiness: -1000 }
interface Coke extends Soda { fizziness: 1000 }
interface DrPepper extends Soda { fizziness: 800 }
interface Pepsi extends Soda { fizziness: 750 }

type Drinks = Water | Lemonade | Tea | Soda | Coke | DrPepper | Pepsi;

Now, I want a type that is all the drinks, except for the extremely non-specific "Soda" drink.

While I could just exclude that and then bring back the other (more specific) sodas:

type NoGenericSodaBad = Exclude<Drinks, Soda> | Coke | DrPepper | Pepsi;

What I really need is a stricter Exclude:

type StrictExclude<T, U> = T extends U ? U extends T ? never : T : T;

This type excludes U and only U (although if you pass never it'll for sure break some things) since each member in the union T must be mutually assignable to U. That means I can get my drinks without "Soda"!

type NoGenericSodaGood = StrictExclude<Drinks, Soda>;

Playground

Upvotes: 4

Related Questions