Условные типы
Условные типы выбирают один из двух выходных типов на основе проверки на уровне типов: CheckType extends ExtendsType ? TrueType : FalseType.
type IsString<T> = T extends string ? 'yes' : 'no'; type A = IsString<string>; // 'yes' type B = IsString<number>; // 'no'
Условные типы по форме и большинству свойств совпадают с TypeScript: распределение по объединениям, infer в правой части extends (включая форму ограничения infer T extends Bound) и [T] extends [...] для отключения распределения работают одинаково.
Когда это использовать
Используйте условные типы, когда тип должен зависеть от другого типа — например, чтобы извлечь части обобщённого типа или выразить типы возвращаемых значений, которые меняются в зависимости от входных данных. В простых случаях, когда конкретные типы известны заранее, обычно понятнее использовать обычное объединение. Для функций с несколькими сигнатурами вместо этого рассмотрите перегрузки.
Базовое использование
Если CheckType является подтипом ExtendsType, условный тип будет вычислен как TrueType. В противном случае он будет вычислен как FalseType.
Следующий пример иллюстрирует оба случая.
class Animal {}
class Dog extends Animal {}
type TypeofAnimal = Dog extends Animal ? 'animal' : 'unknown'; // evaluates to 'animal'
type TypeofString = string extends Animal ? 'animal' : 'unknown'; // evaluates to 'unknown' Обобщённые условные типы
Возможно, это не кажется особенно полезным, поскольку вы уже знаете, к какому типу будет выполнено вычисление. Однако, комбинируя условные типы с обобщениями, можно выполнять сложные вычисления над типами. Например, можно описать оператор typeof на уровне типов:
type TypeOf<T> =
T extends null ? 'null' :
T extends void ? 'undefined' :
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends (...ReadonlyArray<empty>)=>unknown ? 'function' : 'object'
type T1 = TypeOf<null>; // evaluates to 'null'
type T2 = TypeOf<void>; // evaluates to 'undefined'
type T3 = TypeOf<string>; // evaluates to 'string'
type T4 = TypeOf<number>; // evaluates to 'number'
type T5 = TypeOf<boolean>; // evaluates to 'boolean'
type T6 = TypeOf<(string)=>boolean>; // evaluates to 'function'
type T7 = TypeOf<{foo: string}>; // evaluates to 'object' Типы возвращаемых значений функций, зависящие от входных типов
Условные типы также позволяют интуитивно описывать условия выбора разных перегрузок функций:
declare function wrap<T>(value: T): T extends string ? { type: 'string', value: string }
: T extends number ? { type: 'number', value: number }
: { type: 'unsupported' }
const v1 = wrap(3); // has type { type: 'number', value: number }
const v2 = wrap('4'); // has type { type: 'string', value: string }
const v3 = wrap({}); // has type { type: 'unsupported' } Пример выше также можно записать с помощью перегрузки функции:
declare function wrap(value: string): { type: 'string', value: string }
declare function wrap(value: number): { type: 'number', value: number }
declare function wrap(value: unknown): { type: 'unsupported' }
const v1 = wrap(3); // has type { type: 'number', value: number }
const v2 = wrap('4'); // has type { type: 'string', value: string }
const v3 = wrap({}); // has type { type: 'unsupported' } Выведение типов внутри условных типов
С помощью условных типов можно извлекать части типа, используя типы infer. Например, встроенный тип ReturnType реализован на основе условных типов:
type ReturnType<T> = T extends (...args: ReadonlyArray<empty>) => infer Return ? Return : empty; type N = ReturnType<(string) => number>; // evaluates to `number` type S = ReturnType<(number) => string>; // evaluates to `string`
Здесь мы использовали тип infer, чтобы ввести новую переменную обобщённого типа с именем Return, которую можно использовать в ветви типа условного типа. Типы infer могут встречаться только в правой части условия extends в условных типах. Flow выполнит проверку подтипов между проверяемым типом и типом extends, чтобы автоматически определить его тип на основе входного типа T.
В примере с type N = ReturnType<(string) => number> Flow проверяет, является ли (string) => number подтипом (...args: ReadonlyArray<empty>) => infer Return, и в ходе этой проверки Return ограничивается типом number.
При извлечении типов, как в примере выше, обычно нужно, чтобы условный тип всегда выбирал истинную ветвь, когда тип успешно извлечён. Например, молчаливый выбор ложной ветви — не лучший вариант:
type ExtractReturnTypeNoValidation<T> = T extends (...args: ReadonlyArray<empty>) => infer Return ? Return : any; 1 as ExtractReturnTypeNoValidation<string>; // no error :(
Вместо этого можно заставить Flow выдавать ошибку, если входное значение не является типом функции. Для этого можно добавить ограничения к параметру типа:
type ReturnType<T extends (...args: ReadonlyArray<empty>) => unknown> = T extends (...args: ReadonlyArray<empty>) => infer Return ? Return : any; 1 as ReturnType<(string) => number>; 1 as ReturnType<string>; // Error
Ограничение выведенных типов с помощью extends
Переменная infer может иметь собственное ограничение, записываемое как infer T extends Bound. Это ограничение выполняет две задачи.
Оно служит фильтром для типа-кандидата. Если тип, сопоставленный с infer X, не удовлетворяет ограничению, проверка extends завершается неудачей, и условный тип вычисляется по ложной ветви:
type ElementIfNumeric<T> = T extends Array<infer X extends number> ? X : false; type A = ElementIfNumeric<Array<number>>; // evaluates to number type B = ElementIfNumeric<Array<string>>; // string violates the bound, so evaluates to false 1 as A; // OK 1 as B; // ERROR: number is not false
Оно также задаёт значение по умолчанию. Если переменная infer имеет недостаточно ограничений, поскольку сопоставленный тип не позволяет её определить, она принимает значение своего ограничения, а не mixed:
// Without a bound, an underconstrained infer defaults to mixed: type Unbounded<T> = T extends null | [infer X] ? X : number; type R1 = Unbounded<null>; // X is never matched, so evaluates to mixed 1 as R1; // OK // With a bound, it defaults to that bound instead: type Bounded<T> = T extends null | [infer X extends string] ? X : number; type R2 = Bounded<null>; // X is never matched, so evaluates to string '' as R2; // OK 1 as R2; // ERROR: number is not string
Распределяемые условные типы
Если обобщённый условный тип получает объединение в качестве аргумента типа, условие распределяется по элементам объединения. Например, пример с TypeOf выше можно применить к объединению:
type TypeOf<T> = T extends null ? 'null' : T extends void ? 'undefined' : T extends string ? 'string' : 'other'; type StringOrNull = TypeOf<string | null>; // evaluates to 'string' | 'null'
Сначала тип объединения разбивается на отдельные типы, а затем каждый из них передаётся условному типу для отдельного вычисления. В примере выше это выглядит примерно так:
TypeOf<string | null> --> (break up the union) --> TypeOf<string> | TypeOf<null> --> (evaluate each conditional type separately) --> 'string' | 'null'
Чтобы избежать такого поведения, можно обернуть проверяемый тип и тип extends в унарный тип-кортеж:
type NonDistributiveTypeOf<T> = [T] extends [null] ? 'null' : [T] extends [void] ? 'undefined' : [T] extends [string] ? 'string' : 'other'; type Other = NonDistributiveTypeOf<string | null>; // evaluates to 'other'
Этот приём работает, потому что Flow включает распределение условного типа, только если проверяемый тип является обобщённым типом. Пример выше не выбирает ни одну истинную ветвь условного типа, поскольку [string | null] не является подтипом [null], [void] или [string], так как типизация кортежей является инвариантной.
Внедрение
Чтобы использовать условные типы, необходимо обновить инфраструктуру, чтобы она поддерживала этот синтаксис:
-
prettier: версия 3 или более поздняя, с установленным плагином@prettier/plugin-hermes(см. эти инструкции). -
babelсflow-parser/babel-plugin. Инструкции по настройке см. в руководстве по Babel. -
eslintсflow-eslint. Инструкции по настройке см. в руководстве по ESLint.
См. также
- Отображаемые типы — ещё один продвинутый тип для преобразования типов объектов
- Типы индексированного доступа — извлечение типов свойств, часто в сочетании с условными типами
- Обобщения — условные типы особенно полезны с параметрами обобщённых типов
-
Вспомогательные типы — встроенные преобразования типов, такие как
Partial,ReadonlyиPick
© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/types/conditional