Reputation: 2013
I'm using disjoint union types to represent events, as is recommended for Redux-like actions. Generally this is working well, but in some parts of my app, events have an additional timestamp field. How do I annotate the type of a timestamped event without duplicating something?
I tried using intersection types to merge the additional required property, but the following fails:
/* @flow */
export type EvtA = {type: 'A', prop1: string};
export type EvtB = {type: 'B', prop2: string};
export type Event =
| EvtA
| EvtB;
type Timestamped = { timestamp: number };
type TSEvent = Event & Timestamped;
function show(event : TSEvent) {
console.log(event.timestamp);
// let event = ((e: any): Event);
if (event.type === 'A') {
console.log(event.prop1);
}
}
Error (on http://flow.org/try):
function show(event : TSEvent) {
^ all branches are incompatible: Either property `prop1` is missing in `EvtB` [1] but exists in `EvtA` [2]. Or property `prop1` is missing in `Timestamped` [3] but exists in `EvtA` [2]. Or property `prop2` is missing in `EvtA` [1] but exists in `EvtB` [4]. Or property `prop2` is missing in `Timestamped` [3] but exists in `EvtB` [4].
References:
12: type TSEvent = Event & Timestamped;
^ [1]
7: | EvtA ^ [2]
12: type TSEvent = Event & Timestamped;
^ [3]
8: | EvtB;
^ [4]
17: console.log(event.prop1);
^ Cannot get `event.prop1` because: Either property `prop1` is missing in `EvtB` [1]. Or property `prop1` is missing in `Timestamped` [2].
References:
12: type TSEvent = Event & Timestamped;
^ [1]
12: type TSEvent = Event & Timestamped;
^ [2]
The commented-out typecast is my current hacky workaround.
(Yes, perhaps the cleaner approach would have been type LogEntry = { event: Event, timestamp: number }
, but that requires changing a lot of other code.)
Upvotes: 1
Views: 220
Reputation: 3478
What you're probably looking for is an object spread:
(Try)
/* @flow */
export type EvtA = {type: 'A', prop1: string};
export type EvtB = {type: 'B', prop2: string};
export type Event =
| EvtA
| EvtB;
type Timestamped = {timestamp: number };
type TSEventA = {
...EvtA,
...Timestamped,
};
// TSEventA now has type:
// {prop1?: mixed, timestamp?: mixed, type?: mixed}
function show(event : TSEventA) {
console.log(event.timestamp);
// let event = ((e: any): Event);
if (event.type === 'A') {
console.log(event.prop1);
}
}
You can retain all the type information by spreading exact objects:
(Try)
/* @flow */
export type EvtA = {|type: 'A', prop1: string|};
export type EvtB = {|type: 'B', prop2: string|};
export type Event =
| EvtA
| EvtB;
type Timestamped = {|timestamp: number|};
type TSEvent = {
...Event,
...Timestamped
};
// TSEvent now has union type:
// {prop2: string, timestamp: number, type: "B"}
// | {prop1: string, timestamp: number, type: "A"}
function show(event : TSEvent) {
console.log(event.timestamp);
if (event.type === 'A') {
console.log('A', event.prop1);
} else if (event.type === 'B') {
console.log('B', event.prop2);
}
}
Upvotes: 1