Unions
Sometimes it's useful to create a type which is one of a set of other types. For example, you might want to write a function which accepts a set of primitive value types. For this Flow supports union types.
1function toStringPrimitives(value: number | boolean | string): string {2 return String(value);3}4
5toStringPrimitives(1); // Works!6toStringPrimitives(true); // Works!7toStringPrimitives('three'); // Works!8
9toStringPrimitives({prop: 'val'}); // Error! 10toStringPrimitives([1, 2, 3, 4, 5]); // Error!
9:20-9:32: Cannot call `toStringPrimitives` with object literal bound to `value` because: [incompatible-call] Either object literal [1] is incompatible with number [2]. Or object literal [1] is incompatible with boolean [3]. Or object literal [1] is incompatible with string [4].10:20-10:34: Cannot call `toStringPrimitives` with array literal bound to `value` because: [incompatible-call] Either array literal [1] is incompatible with number [2]. Or array literal [1] is incompatible with boolean [3]. Or array literal [1] is incompatible with string [4].
Union type syntax
Union types are any number of types which are joined by a vertical bar |
.
Type1 | Type2 | ... | TypeN
You may also add a leading vertical bar which is useful when breaking union types onto multiple lines.
type Foo =
| Type1
| Type2
| ...
| TypeN
Each of the members of a union type can be any type, even another union type.
1type Numbers = 1 | 2;2type Colors = 'red' | 'blue'3
4type Fish = Numbers | Colors;
If you have enabled Flow Enums, they may be an alternative to unions of literal types.
Union shorthands
The union of some type T
with null
or void
is common, so we provide a shorthand called maybe types, by using the ?
prefix. The type ?T
is equivalent to T | null | void
:
1function maybeString(x: ?string) { /* ... */ }2maybeString('hi'); // Works!3maybeString(null); // Works!4maybeString(undefined); // Works!
The union of every single type that exists is the mixed
type:
1function everything(x: mixed) { /* ... */ }2everything(1); // Works!3everything(true); // Works!4everything(null); // Works!5everything({foo: 1}); // Works!6everything(new Error()); // Works!
Unions & Refinements
When you have a value which is a union type it's often useful to break it apart and handle each individual type separately. With union types in Flow you can refine the value down to a single type.
For example, if we have a value with a union type that is a number
, a
boolean
, or a string
, we can treat the number case separately by using
JavaScript's typeof
operator.
1function toStringPrimitives(value: number | boolean | string) {2 if (typeof value === 'number') {3 return value.toLocaleString([], {maximumSignificantDigits: 3}); // Works!4 }5 // ...6}
By checking the typeof
our value and testing to see if it is a number
, Flow
knows that inside of that block it is only a number. We can then write code
which treats our value as a number inside of that block.
Union types requires one in, but all out
When calling a function that accepts a union type we must pass in one of those types. But inside of the function we are required to handle all of the possible types.
Let's rewrite the function to handle each type individually using refinements.
1function toStringPrimitives(value: number | boolean | string): string {2 if (typeof value === 'number') {3 return String(value);4 } else if (typeof value === 'boolean') {5 return String(value);6 }7 return value; // If we got here, it's a `string`!8}
If we do not handle each possible type of our value, Flow will give us an error:
1function toStringPrimitives(value: number | boolean | string): string {2 if (typeof value === 'number') {3 return String(value);4 }5 return value; // Error! 6}
5:10-5:14: Cannot return `value` because boolean [1] is incompatible with string [2]. [incompatible-return]
Disjoint Object Unions
There's a special type of union in Flow known as a "disjoint object union" which can be used with refinements. These disjoint object unions are made up of any number of object types which are each tagged by a single property.
For example, imagine we have a function for handling a response from a server
after we've sent it a request. When the request is successful, we'll get back
an object with a type
property set to 'success'
and a value
that we've
updated.
{type: 'success', value: 23}
When the request fails, we'll get back an object with type
set to 'error'
and an error
property describing the error.
{type: 'error', error: 'Bad request'}
We can try to express both of these objects in a single object type. However,
we'll quickly run into issues where we know a property exists based on the
type
property but Flow does not.
1type Response = {2 type: 'success' | 'error',3 value?: number,4 error?: string5};6
7function handleResponse(response: Response) {8 if (response.type === 'success') {9 const value: number = response.value; // Error! 10 } else {11 const error: string = response.error; // Error! 12 }13}
9:27-9:40: Cannot assign `response.value` to `value` because undefined [1] is incompatible with number [2]. [incompatible-type]11:27-11:40: Cannot assign `response.error` to `error` because undefined [1] is incompatible with string [2]. [incompatible-type]
Trying to combine these two separate types into a single one will only cause us trouble.
Instead, if we create a union type of both object types, Flow will be able to
know which object we're using based on the type
property.
1type Response =2 | {type: 'success', value: number}3 | {type: 'error', error: string};4
5function handleResponse(response: Response) {6 if (response.type === 'success') {7 const value: number = response.value; // Works!8 } else {9 const error: string = response.error; // Works!10 }11}
In order to use this pattern, there must be a key that is in every object in your union (in our example above, type
),
and every object must set a different literal type for that key (in our example, the string 'success'
, and the string 'error'
).
You can use any kind of literal type, including numbers and booleans.
Disjoint object unions with exact types
Disjoint unions require you to use a single property to distinguish each object type. You cannot distinguish two different inexact objects by different properties.
1type Success = {success: true, value: boolean, ...};2type Failed = {error: true, message: string, ...};3
4function handleResponse(response: Success | Failed) {5 if (response.success) {6 const value: boolean = response.value; // Error! 7 }8}
6:37-6:41: Cannot get `response.value` because property `value` is missing in `Failed` [1]. [prop-missing]
This is because in Flow it is okay to pass an object value with more properties than the inexact object type expects (because of width subtyping).
1type Success = {success: true, value: boolean, ...};2type Failed = {error: true, message: string, ...};3
4function handleResponse(response: Success | Failed) {5 // ...6}7
8handleResponse({9 success: true,10 error: true,11 value: true,12 message: 'hi'13});
Unless the objects somehow conflict with one another there is no way to distinguish them.
However, to get around this you could use exact object types.
1type Success = {success: true, value: boolean};2type Failed = {error: true, message: string};3
4type Response = Success | Failed;5
6function handleResponse(response: Response) {7 if (response.success) {8 const value: boolean = response.value;9 } else {10 const message: string = response.message;11 }12}
With exact object types, we cannot have additional properties, so the objects conflict with one another and we are able to distinguish which is which.
Disjoint tuple unions
Like disjoint object unions explained above, you can also define disjoint tuple unions (support in Flow version ≥0.240). These are unions of tuple types, where each tuple is tagged by a particular element. For example:
1type Response =2 | ['success', number]3 | ['error', string];4
5function handleResponse(response: Response) {6 if (response[0] === 'success') {7 const value: number = response[1]; // Works!8 } else {9 const error: string = response[1]; // Works!10 }11}
This feature is particularly useful for function arguments, which are tuples. Note the use of tuple element labels to make the code more clear.
1function prettyPrint(2 ...args: ['currency', dollars: number, cents: number]3 | ['choice', boolean]4): string {5 switch (args[0]) {6 case 'currency':7 return args[1] + '.' + args[2];8 case 'choice':9 return args[1] ? 'yes' : 'no';10 }11}12// Argument types based on the first arg13prettyPrint("currency", 1, 50); // OK14prettyPrint("choice", true); // OK15
16prettyPrint("currency", 1); // ERROR - missing arg 17prettyPrint("currency", true); // ERROR - wrong type arg
16:1-16:11: Cannot call `prettyPrint` because: [incompatible-call] Either string [1] is incompatible with string literal `choice` [2] in index 0. Or rest array [3] has 2 elements but tuple type [4] has 3 elements.17:1-17:11: Cannot call `prettyPrint` because: [incompatible-call] Either string [1] is incompatible with string literal `choice` [2] in index 0. Or rest array [3] has 2 elements but tuple type [4] has 3 elements.