Обобщённые типы
Обобщённые типы (или полиморфные типы) позволяют писать функции, классы и псевдонимы типов, которые работают с любым типом и при этом сохраняют связи между типами.
function identity<T>(value: T): T {
return value;
} Есть два отличия от 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