Spec-Zone.ru › Flow

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

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

function identity<T>(value: T): T {
  return value;
}
Сравнение с TypeScript

Есть два отличия от TypeScript: аргументы обобщённых типов нельзя опускать, даже если у каждого параметра есть значение по умолчанию — пишите Foo<>, а не Foo. Кроме того, параметры типов по умолчанию инвариантны, поэтому преобразование C<number> в C<number | string> будет отклонено; чтобы задать вариантность, используйте out T / in T. TS выводит вариантность на основе использования, с учётом исключений для совместимости, из-за чего статические гарантии для полей, доступных и для чтения, и для записи, могут быть слабее.

Когда это использовать​

Используйте обобщённые типы, если функция, класс или псевдоним типа должен работать с несколькими типами, сохраняя связь между входными и выходными данными. Если тип не имеет значения и отслеживать его не нужно, используйте вместо этого unknown. Если есть фиксированное количество конкретных типов, понятнее может быть объединение.

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

Синтаксис обобщённых типов​

В синтаксисе есть несколько мест, где встречаются обобщённые типы.

Функции с обобщёнными типами​

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

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

function method<T>(param: T): T {
  return param;
}


const f = function<T>(param: T): T {
  return param;
}

Типы функций с обобщёнными типами​

Типы функций могут создавать обобщённые типы так же, как обычные функции: для этого перед списком параметров типа функции добавляется список параметров типа <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,
}

Передача аргументов типа вызываемым объектам​

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

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


doSomething<number>(3);

Аргументы типа можно также напрямую передавать обобщённым классам в выражении new:

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

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

class GenericClass<T, U=string, V=number>{}
const c = new GenericClass<boolean, _, string>();

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

Поведение обобщённых типов​

Обобщённые типы работают как переменные​

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

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

Создавайте столько обобщённых типов, сколько нужно​

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

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

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

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

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

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

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


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

Добавление типов к обобщённым типам​

Как и у unknown, у обобщённых типов есть тип «неизвестно». Нельзя использовать обобщённый тип так, как если бы он был конкретным типом.

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

Можно уточнить тип, но обобщённый тип всё равно позволит передавать значения любого типа.

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. :(

Вместо этого можно задать ограничение для обобщённого типа с помощью ключевого слова extends.

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


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

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

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


let one: 1 = identity(1);
let two: 2 = identity(2);
let three: "three" = identity("three"); // Error!

Обобщённые типы задают ограничения​

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


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

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

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


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

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

function identity<T extends string>(val: T): T {
  return val;
}


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

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

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


identity('bar');
Устаревший синтаксис ограничений

Ключевое слово extends для ограничений обобщённых типов включено по умолчанию начиная с Flow 0.302. В старом коде может использоваться устаревший синтаксис с двоеточием (например, <T: string> вместо <T extends string>). Подробности перехода см. в разделе Обновление устаревшего синтаксиса.

Параметризованные обобщённые типы​

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

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

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


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

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

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

Классы

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


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

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

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


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

Интерфейсы

interface HasProp<T> {
  prop: T,
}


class Item {
  prop: string;
}


Item.prototype as HasProp<string>; // Works!
Item.prototype as HasProp; // Error!

Значения по умолчанию для параметризованных обобщённых типов​

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

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


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

При использовании типа всегда нужно указывать скобки <> (как круглые скобки при вызове функции). Если написать только имя Item (без <>), возникнет ошибка [missing-type-arg], даже если у каждого параметра есть значение по умолчанию. Имя без аргументов зарезервировано для неприменённого конструктора типа — чтобы применить его, всегда нужно явно указать аргументы типа или пустой <> для использования значений по умолчанию.

Ключевые слова вариантности​

Поведение обобщённого типа при подтипизации также можно задать с помощью ключевых слов вариантности. По умолчанию обобщённые типы инвариантны, но перед объявлением можно добавить ключевое слово out, чтобы сделать их ковариантными, или in, чтобы сделать их контравариантными. Подробнее о вариантности в Flow см. в документации по вариантности.

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

type GenericBox<out T> = T;


const x: GenericBox<number> = 3;
x as GenericBox<number| string>;

Приведённый выше пример невозможно реализовать без ключевого слова вариантности out:

type GenericBoxError<T> = T;


const x: GenericBoxError<number> = 3;
x as GenericBoxError<number| string>; // Error: number | string is not compatible with number.

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

type NotActuallyCovariant<out T> = (T) => void; // Error
Устаревший синтаксис сигилов

Ключевые слова вариантности out и in включены по умолчанию начиная с Flow 0.315. В старом коде могут использоваться устаревшие сигилы + и - (например, <+T> вместо <out T>, <-T> вместо <in T>); они по-прежнему распознаются, но считаются устаревшими. Подробности перехода см. в разделе Обновление устаревшего синтаксиса.

См. также​

  • Псевдонимы типов — объявление именованных типов, часто в сочетании с обобщёнными типами
  • Функции — использование обобщённых типов в сигнатурах функций
  • Вариантность — взаимодействие ковариантности и контравариантности с обобщёнными типами
  • Условные типы — типы, вычисляемые на основе параметров обобщённых типов
  • Отображённые типы — преобразование типов объектов с помощью обобщённых типов
  • Обновление устаревшего синтаксиса Flow — переход от устаревших ограничений <T: Bound> и сигилов вариантности +T / -T к современным формам с ключевыми словами

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

Spec-Zone.ru

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