Spec-Zone.ru › Flow

Unions

A union type represents a value that is one of a set of types.

function size(value: number | string): number {
  return typeof value === 'number' ? value : value.length;
}


size(1);       // Works!
size('three'); // Works!
size(true);    // Error!

When to use this​

Use unions when a value can be one of several types, such as a function that handles different kinds of events or responses. For unions of string or number literals (e.g. 'success' | 'error'), consider using Flow Enums instead, which provide exhaustiveness checking and a more structured API. If you need a nullable type, use the maybe type shorthand ?T rather than writing T | null | void. For values that must satisfy multiple types at once rather than one of them, use intersections instead.

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.

type Numbers = 1 | 2;
type Colors = 'red' | 'blue'


type 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:

function maybeString(x: ?string) { /* ... */ }
maybeString('hi'); // Works!
maybeString(null); // Works!
maybeString(undefined); // Works!

The type that accepts every possible value is unknown:

function everything(x: unknown) { /* ... */ }
everything(1); // Works!
everything(true); // Works!
everything(null); // Works!
everything({foo: 1}); // Works!
everything(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.

function toStringPrimitives(value: number | boolean | string) {
  if (typeof value === 'number') {
    return value.toLocaleString([], {maximumSignificantDigits: 3}); // Works!
  }
  // ...
}

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.

function toStringPrimitives(value: number | boolean | string): string {
  if (typeof value === 'number') {
    return String(value);
  } else if (typeof value === 'boolean') {
    return String(value);
  }
  return value; // If we got here, it's a `string`!
}

If we do not handle each possible type of our value, Flow will give us an error:

function toStringPrimitives(value: number | boolean | string): string {
  if (typeof value === 'number') {
    return String(value);
  }
  return value; // Error!
}

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.

type Response = {
  type: 'success' | 'error',
  value?: number,
  error?: string
};


function handleResponse(response: Response) {
  if (response.type === 'success') {
    const value: number = response.value; // Error!
  } else {
    const error: string = response.error; // Error!
  }
}

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.

type Response =
  | {type: 'success', value: number}
  | {type: 'error', error: string};


function handleResponse(response: Response) {
  if (response.type === 'success') {
    const value: number = response.value; // Works!
  } else {
    const error: string = response.error; // Works!
  }
}

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 unions with generic types​

Disjoint union refinement requires the discriminant property to have a literal type. When the discriminant comes from a generic type parameter, Flow cannot narrow the union because the generic hasn't been resolved to a specific literal yet:

type PayloadMap = {
  PING: string,
  PONG: number,
};


function handle<T extends keyof PayloadMap>(
  type: T,
  payload: PayloadMap[T],
) {
  if (type === 'PING') {
    payload as string; // Error! payload is still PayloadMap[T]
  }
}

To work around this, restructure the code so the discriminant is a concrete literal type. For example, use a disjoint union directly:

type Message =
  | {type: 'PING', payload: string}
  | {type: 'PONG', payload: number};


function handle(message: Message) {
  if (message.type === 'PING') {
    message.payload as string; // Works!
  }
}

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.

type Success = {success: true, value: boolean, ...};
type Failed  = {error: true, message: string, ...};


function handleResponse(response:  Success | Failed) {
  if (response.success) {
    const value: boolean = response.value; // Error!
  }
}

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).

type Success = {success: true, value: boolean, ...};
type Failed  = {error: true, message: string, ...};


function handleResponse(response:  Success | Failed) {
  // ...
}


handleResponse({
  success: true,
  error: true,
  value: true,
  message: 'hi'
});

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.

type Success = {success: true, value: boolean};
type Failed  = {error: true, message: string};


type Response = Success | Failed;


function handleResponse(response: Response) {
  if (response.success) {
    const value: boolean = response.value;
  } else {
    const message: string = response.message;
  }
}

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. These are unions of tuple types, where each tuple is tagged by a particular element. For example:

type Response =
  | ['success', number]
  | ['error', string];


function handleResponse(response: Response) {
  if (response[0] === 'success') {
    const value: number = response[1]; // Works!
  } else {
    const error: string = response[1]; // Works!
  }
}

This feature is particularly useful for function arguments, which are tuples. Note the use of tuple element labels to make the code more clear.

function prettyPrint(
  ...args: ['currency', dollars: number, cents: number]
         | ['choice', boolean]
): string {
  switch (args[0]) {
    case 'currency':
      return args[1] + '.' + args[2];
    case 'choice':
      return args[1] ? 'yes' : 'no';
  }
}
// Argument types based on the first arg
prettyPrint("currency", 1, 50); // OK
prettyPrint("choice", true); // OK


prettyPrint("currency", 1); // ERROR - missing arg
prettyPrint("currency", true); // ERROR - wrong type arg

See Also​

  • Intersections — the dual of unions: values that are all of a set of types
  • Refinements — how to narrow union types to specific members
  • Maybe Types — shorthand for T | null | void
  • Literal Types — using specific values as types, commonly combined with unions
  • Flow Enums — a structured alternative to unions of literal types
  • Match Expressions — pattern matching for exhaustively handling union members

© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/types/unions

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API