Spec-Zone.ru › Flow

Обобщенные типы

Обобщения (иногда называемые полиморфными типами) — это способ абстрагирования типа.

Представьте, что вы пишете следующую identity функцию, которая возвращает то значение, которое было передано.

function identity(value) {
  return value;
}

У нас возникнут проблемы при попытке написать конкретные типы для этой функции, поскольку она может быть любой.

function identity(value: string): string {
  return value;
}

Вместо этого мы можем создать обобщенный (или полиморфный тип) в нашей функции и использовать его вместо других типов.

function identity<T>(value: T): T {
  return value;
}

Обобщения могут использоваться в функциях, типах функций, классах, псевдонимах типов и интерфейсах.

Предупреждение: Flow не выводит обобщенные типы. Если вы хотите, чтобы что-то имело обобщенный тип, укажите его. В противном случае Flow может вывести тип, который менее полиморфен, чем ожидается.

В следующем примере мы забываем правильно указать identity обобщенный тип, поэтому у нас возникают проблемы, когда мы пытаемся присвоить его func. С другой стороны, genericIdentity правильно типизирован, и мы можем использовать его как ожидалось.

// @flow

type IdentityWrapper = {
  func<T>(T): T
}

function identity(value) {
  return value;
}

function genericIdentity<T>(value: T): T {
  return value;
}

// $ExpectError
const bad: IdentityWrapper = { func: identity }; // Error!
const good: IdentityWrapper = { func: genericIdentity }; // Works!

Синтаксис обобщений

Существует ряд различных мест, где обобщенные типы появляются в синтаксисе.

Функции с обобщениями

Функции могут создавать обобщения, добавляя список параметров типа <T> перед списком параметров функции.

Вы можете использовать обобщения в тех же местах, где вы бы добавили любой другой тип в функцию (параметры или возвращаемые типы).

function method<T>(param: T): T {
  // ...
}

function<T>(param: T): T {
  // ...
}
Типы функций с обобщениями

Типы функций могут создавать обобщения аналогичным образом, как обычные функции, добавляя список параметров типа <T> перед списком параметров типа функции.

Вы можете использовать обобщения в тех же местах, где вы бы добавили любой другой тип в тип функции (параметры или возвращаемые типы).

<T>(param: T) => T

Что затем используется как собственный тип.

function method(func: <T>(param: T) => T) {
  // ...
}
Классы с обобщениями

Классы могут создавать обобщения, поместив список параметров типа перед телом класса.

class Item<T> {
  // ...
}

Вы можете использовать обобщения в тех же местах, где вы бы добавили любой другой тип в класс (типы свойств и параметры/возвращаемые типы методов).

class Item<T> {
  prop: T;

  constructor(param: T) {
    this.prop = param;
  }

  method(): T {
    return this.prop;
  }
}
Псевдонимы типов с обобщениями
type Item<T> = {
  foo: T,
  bar: T,
};
Интерфейсы с обобщениями
interface Item<T> {
  foo: T,
  bar: T,
}
Предоставление аргументов типа вызываемым объектам

Вы можете предоставить вызываемым объектам аргументы типа для их обобщений непосредственно в вызове:

//@flow
function doSomething<T>(param: T): T {
  // ...
  return param;
}

doSomething<number>(3);

Вы также можете предоставить обобщенным классам аргументы типа непосредственно в выражении %%%CODE_BLOCK_18%%:

//@flow
class GenericClass<T> {}
const c = new GenericClass<number>();

Если вы хотите указать только некоторые из аргументов типа, вы можете использовать _, чтобы позволить Flow вывести для вас тип:

//@flow
class GenericClass<T, U, V>{}
const c = new GenericClass<_, number, _>()

Предупреждение: В целях производительности всегда рекомендуется указывать конкретные аргументы, когда это возможно. _ не небезопасно, но медленнее, чем явное указание аргументов типа.

Поведение обобщений

Обобщения ведут себя как переменные

Обобщенные типы работают очень похоже на переменные или параметры функций, за исключением того, что они используются для типов. Вы можете использовать их, когда они находятся в области видимости.

function constant<T>(value: T): () => T {
  return function(): T {
    return value;
  };
}

Создайте столько обобщений, сколько вам нужно

В списке параметров типа вы можете иметь столько обобщений, сколько вам нужно, называя их как угодно:

function identity<One, Two, Three>(one: One, two: Two, three: Three) {
  // ...
}

Обобщения отслеживают значения

При использовании обобщенного типа для значения Flow будет отслеживать значение и убедиться, что вы его не заменяете на другое.

// @flow
function identity<T>(value: T): T {
  // $ExpectError
  return "foo"; // Error!
}

function identity<T>(value: T): T {
  // $ExpectError
  value = "foo"; // Error!
  // $ExpectError
  return value;  // Error!
}

Flow отслеживает конкретный тип значения, которое вы передаете через обобщение, позволяя вам использовать его позже.

// @flow
function identity<T>(value: T): T {
  return value;
}

let one: 1 = identity(1);
let two: 2 = identity(2);
// $ExpectError
let three: 3 = identity(42);

Добавление типов к обобщениям

Аналогично mixed, обобщения имеют «неизвестный» тип. Вам не разрешается использовать обобщение так, как будто это конкретный тип.

// @flow
function logFoo<T>(obj: T): T {
  // $ExpectError
  console.log(obj.foo); // Error!
  return obj;
}

Вы могли бы уточнить тип, но обобщение по-прежнему позволит передавать любой тип.

// @flow
function logFoo<T>(obj: T): T {
  if (obj && obj.foo) {
    console.log(obj.foo); // Works.
  }
  return obj;
}

logFoo({ foo: 'foo', bar: 'bar' });  // Works.
logFoo({ bar: 'bar' }); // Works. :(

Вместо этого вы можете добавить тип к своему обобщению так же, как и параметр функции.

// @flow
function logFoo<T: { foo: string }>(obj: T): T {
  console.log(obj.foo); // Works!
  return obj;
}

logFoo({ foo: 'foo', bar: 'bar' });  // Works!
// $ExpectError
logFoo({ bar: 'bar' }); // Error!

Таким образом, вы можете сохранить поведение обобщений, разрешая использовать только определенные типы.

// @flow
function identity<T: number>(value: T): T {
  return value;
}

let one: 1 = identity(1);
let two: 2 = identity(2);
// $ExpectError
let three: "three" = identity("three");

Обобщенные типы действуют как ограничения

// @flow
function identity<T>(val: T): T {
  return val;
}

let foo: 'foo' = 'foo';           // Works!
let bar: 'bar' = identity('bar'); // Works!

В Flow, в большинстве случаев, когда вы передаете один тип в другой, вы теряете исходный тип. Поэтому, когда вы передаете конкретный тип в менее конкретный, Flow «забывает», что он когда-то был чем-то более конкретным.

// @flow
function identity(val: string): string {
  return val;
}

let foo: 'foo' = 'foo';           // Works!
// $ExpectError
let bar: 'bar' = identity('bar'); // Error!

Обобщения позволяют удерживать более конкретный тип, добавляя ограничение. Таким образом, типы обобщений действуют как «ограничения».

// @flow
function identity<T: string>(val: T): T {
  return val;
}

let foo: 'foo' = 'foo';           // Works!
let bar: 'bar' = identity('bar'); // Works!

Обратите внимание, что когда у вас есть значение с обобщенным типом с ограничением, вы не можете использовать его так, как будто это более конкретный тип.

// @flow
function identity<T: string>(val: T): T {
  let str: string = val; // Works!
  // $ExpectError
  let bar: 'bar'  = val; // Error!
  return val;
}

identity('bar');

Параметризованные обобщения

Иногда обобщения позволяют передавать типы как аргументы функции. Они известны как параметризованные обобщения (или параметрический полиморфизм).

Например, псевдоним типа с обобщением параметризован. При его использовании вам нужно будет указать аргумент типа.

type Item<T> = {
  prop: T,
}

let item: Item<string> = {
  prop: "value"
};

Вы можете представить это как передачу аргументов функции, только возвращаемое значение — это тип, который вы можете использовать.

Классы (при использовании как тип), псевдонимы типов и интерфейсы требуют, чтобы вы передавали аргументы типа. Функции и типы функций не имеют параметризованных обобщений.

Классы

// @flow
class Item<T> {
  prop: T;
  constructor(param: T) {
    this.prop = param;
  }
}

let item1: Item<number> = new Item(42); // Works!
// $ExpectError
let item2: Item = new Item(42); // Error!

Псевдонимы типов

// @flow
type Item<T> = {
  prop: T,
};

let item1: Item<number> = { prop: 42 }; // Works!
// $ExpectError
let item2: Item = { prop: 42 }; // Error!

Интерфейсы

// @flow
interface HasProp<T> {
  prop: T,
}

class Item {
  prop: string;
}

(Item.prototype: HasProp<string>); // Works!
// $ExpectError
(Item.prototype: HasProp); // Error!
Добавление значений по умолчанию параметризованным обобщениям

Вы также можете предоставить значения по умолчанию для параметризованных обобщений, как и параметры функции.

type Item<T: number = 1> = {
  prop: T,
};

let foo: Item<> = { prop: 1 };
let bar: Item<2> = { prop: 2 };

Вы всегда должны включать скобки <> при использовании типа (точно так же, как скобки для вызова функции).

Символы вариативности

Вы также можете указать поведение суботипирования обобщения с помощью символов вариативности. По умолчанию обобщения ведут себя инвариантно, но вы можете добавить + к их объявлению, чтобы они вели себя ковариантно, или - к их объявлению, чтобы они вели себя контравариантно. См. наши документы по вариативности для получения дополнительной информации о вариативности в Flow.

Символы вариативности позволяют более точно указать, как вы намерены использовать свои обобщения, предоставляя Flow возможность выполнения более точной проверки типов. Например, вы можете захотеть, чтобы это соотношение выполнялось:

//@flow
type GenericBox<+T> = T;

var x: GenericBox<number> = 3;
(x: GenericBox<number| string>);

Вышеприведенный пример нельзя было бы выполнить без символа вариативности %%%CODE_BLOCK_45%%:

//@flow
type GenericBoxError<T> = T;

var x: GenericBoxError<number> = 3;
(x: GenericBoxError<number| string>); // number | string is not compatible with number.

Обратите внимание, что если вы аннотируете свой обобщенный тип с символами вариативности, Flow проверит, чтобы убедиться, что эти типы появляются только в позициях, которые имеют смысл для этого символа вариативности. Например, вы не можете объявить параметр обобщенного типа для ковариантного поведения и использовать его в контравариантной позиции:

//@flow
type NotActuallyCovariant<+T> = (T) => void;

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

Spec-Zone.ru

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