janhartmann
janhartmann

Reputation: 15003

Record of discriminated union

Taken this structured messages:

type MessageVariant<T extends string, P = {}> = {
  type: T;
  payload: P;
};

type BoundsChangedMessage = MessageVariant<
  "bounds_changed",
  {
    bounds: number[][];
  }
>;

type ChangeLayoutMessage = MessageVariant<
  "change_layout",
  {
    name: string;
  }
>;

type Message = BoundsChangedMessage | ChangeLayoutMessage;

I want to be able to express a strong record of:

const handlers: MessageHandler = {
  bounds_changed: (message/*: BoundsChangedMessage*/) => {},
  change_layout: (message/*: ChangeLayoutMessage*/) => {},
};

So far I have been trying various attempts without luck, including:

type MessageHandler = {
  [T in Message["type"]]: (message: MessageVariant<T, /* Payload? */>) => void;
}

Here is a TS Playground for the example.

Upvotes: 4

Views: 163

Answers (2)

Dzianis Roi
Dzianis Roi

Reputation: 925

You need to use Key mapping via "as" and change your MessageHandler type definition to the following

type Message = BoundsChangedMessage | ChangeLayoutMessage;

type MessageHandler = {
  [M in Message as M["type"]]: (message: M) => void;
}

Here is the link to TS Playground with the full example

Upvotes: 3

Praveenkumar
Praveenkumar

Reputation: 2182

You can achieve that record type by defining the message handler type like below.

type Message = BoundsChangedMessage | ChangeLayoutMessage;

type MessageHandler = {
  [K in Message as K['type']]: (message: K) => void;
}

const handlers: MessageHandler = {
  bounds_changed: m => {
    console.log(m);
  },
  change_layout: m => {
    console.log(m)
  }
}

Upvotes: 1

Related Questions