Функции проверки типов
Функции проверки типов — это функции, тип возвращаемого значения которых аннотирован как param is PredicateType; при возврате true они сужают тип параметра.
function predicate(param: InputType): param is PredicateType {
return <some_expression>;
} Тип этой функции также можно записать в виде аннотации типа:
type PredicateFunc = (param: InputType) => param is PredicateType;
В отличие от TypeScript, Flow проверяет тело функции-предиката x is T в обоих направлениях (положительном и отрицательном) и запрещает переназначение параметра на пути к return. TypeScript проверяет, связан ли тип предиката с типом параметра, но не проверяет тело функции. Если выполняется только положительное направление, в Flow есть implies x is T (односторонние проверки типов) — возможность, доступная только в Flow и не имеющая аналога в TS. Полное сравнение см. в разделе Flow проверяет тела функций проверки типов.
Когда это использовать
Используйте функции проверки типов, когда встроенных уточнений типов (typeof, instanceof, проверок на равенство) недостаточно для сужения типа. Они позволяют инкапсулировать пользовательскую логику сужения типов в повторно используемую функцию — например, фильтровать массивы до конкретного подтипа с помощью .filter() или проверять предметно-ориентированные инварианты, которые Flow не может вывести самостоятельно. Используйте односторонние функции проверки типов (implies), если предикат имеет смысл только в случае истинного результата.
Основное использование
Рассмотрим простой пример: определим функцию проверки типа и используем её для уточнения некоторых значений.
Определение функции проверки типа
type A = { type: "A"; data: string };
type B = { type: "B"; data: number };
type AorB = A | B;
function isA(value: AorB): value is A {
return value.type === "A";
} Мы определили тип данных AorB, представляющий собой непересекающееся объединение типов A и B, каждый из которых имеет свойство type, используемое в качестве метки.
Мы также написали функцию пользовательской проверки типа isA, определённую для объектов типа AorB. Эта функция возвращает true, если значение свойства type входных данных равно "A". Используя определения A и B, Flow может доказать, что если значение type равно "A", то тип value будет A.
Использование функции проверки типа для уточнения значений
Функции с объявленной проверкой типа можно использовать для уточнения значений в условных выражениях. В приведённом выше примере мы можем использовать isA, чтобы сузить тип переменной AorB до A:
type A = { type: "A"; data: string };
type B = { type: "B"; data: number };
type AorB = A | B;
function isA(value: AorB): value is A {
return value.type === "A";
}
function test(x: AorB) {
if (isA(x)) {
// `x` has now been refined to type A.
// We can assign it variables of type A ...
const y: A = x;
// ...and access A's properties through `x`
const stringData: string = x.data;
// As a sanity check, the following assignment to B will error
const error: B = x; // Error
}
} В ветви then условного выражения if (isA(x)) тип x будет A.
Односторонние функции проверки типов
В некоторых случаях мы можем захотеть объявить, что функция проверки типа уточняет только ветвь then условного выражения. Рассмотрим, например, функцию
function isPositive(n: ?number): boolean {
return n != null && n > 0;
} Если бы мы объявили n is number проверкой типа для этой функции, то в следующем коде смогли бы установить, что n — это null | void в ветви else. Однако это неверно, поскольку n может быть просто неотрицательным числом:
function isPositiveUnsound(n: ?number): n is number {
return n != null && n > 0; // Error
}
declare const n: ?number;
if (isPositiveUnsound(n)) {
// n is number here
} else {
// n would be inferred as null | void, but could actually be a non-negative number
} Односторонние функции проверки типов, которые мы обозначаем как implies param is PredicateType, позволяют указать, что предикат сужает тип только в положительном случае. Например:
function isPositive(n: ?number): implies n is number {
return n != null && n > 0;
} Теперь мы получим следующее поведение:
function isPositive(n: ?number): implies n is number {
return n != null && n > 0;
}
declare const n: ?number;
if (isPositive(n)) {
n as number; // OK: n is number here
} else {
n as ?number; // OK: n is still ?number
}
this проверки типа
Иногда полезно объявить предикат типа для экземпляра класса, у которого вызывается метод. Для этого можно добавить this is Type в качестве аннотации возвращаемого значения этого метода. Вызов этого метода в контексте условного выражения уточнит тип экземпляра до Type.
Например, рассмотрим следующие объявления классов:
declare class Shape {
isCircle(): this is Circle;
isSquare(): this is Square;
}
declare class Circle {
radius: number;
}
declare class Square {
side: number;
} Аннотации проверки типа this позволяют уточнить значение типа Shape до Circle или Square:
declare class Shape {
isCircle(): this is Circle;
isSquare(): this is Square;
}
declare class Circle { radius: number }
declare class Square { side: number }
function area(shape: Shape): number {
if (shape.isCircle()) {
// shape is now a Circle
return Math.PI * shape.radius ** 2;
} else if (shape.isSquare()) {
// shape is now a Square
return shape.side ** 2;
} else {
throw new Error('unknown shape');
}
} Проверку типа this также можно объявить для метода обычного класса. Как и в случае с проверками типов параметров, тело такого метода проверяется на соответствие объявленному типу проверки. Единственные уточнения для this, которые понимает Flow, — это проверки instanceof и вызовы других функций проверки типа this:
class Shape {
isCircle(): this is Circle {
return this instanceof Circle; // OK
}
isCircleWithRadius(): implies this is Circle {
return this.isCircle() && this.name === 'circle'; // OK
}
isSquare(): this is Square {
return true; // Error: Shape is not compatible with Square
}
name: string;
}
class Circle extends Shape { radius: number }
class Square extends Shape { side: number } Примечание: Аннотация проверки типа this разрешена только в аннотации возвращаемого значения нестатических методов классов и интерфейсов. Например, следующие варианты недопустимы:
declare class A {}
declare class D {}
declare class InvalidStatic {
static m(): this is D; // Error
}
type InvalidTypeAlias = (x: unknown) => this is A; // Error
declare function invalidFunction(this: unknown): this is A; // Error
const invalidObject = {
m(): this is A { return true; }, // Error
}; Уточнение с помощью Array.filter
Flow распознаёт вызов filter для массива типа Array<T> с функцией обратного вызова, содержащей проверку типа (value: T) => value is S. На основе этого Flow создаст выходной массив типа Array<S>. Обратите внимание: S должен быть подтипом типа элемента массива T.
Например:
type Success = Readonly<{type: 'success', value: 23}>;
type Error = Readonly<{type: 'error', error: string}>;
type Response =
| Success
| Error
function filterSuccess(response: Array<Response>): Array<Success> {
return response.filter(
(response): response is Success => response.type === 'success'
);
}
function filterError1(response: Array<Response>): Array<Error> {
const result = response.filter(
(response): response is Success => response.type === 'success'
);
return result; // Error
}
function filterError2(response: Array<Response>): Array<Error> {
const result = response.filter(
(response): response is Error => response.type === 'success' // Error
);
return result;
} В filterError1 фильтрация создаёт Array<Success>, несовместимый с ожидаемым типом возвращаемого значения Array<Error>.
В filterError2 предикат response.type === 'success' используется для уточнения Response до Success, а не до Error.
Обратите внимание, что для аргумента .filter() не обязательно указывать аннотацию проверки типа. Она будет выведена из тела стрелочной функции:
type Success = Readonly<{type: 'success', value: 23}>;
type Error = Readonly<{type: 'error', error: string}>;
type Response = Success | Error;
function filterSuccessShort(response: Array<Response>): Array<Success> {
return response.filter(
response => response.type === 'success'
);
} Определение функций проверки типа
Чтобы гарантировать корректность уточнений с помощью функций проверки типа, Flow выполняет ряд проверок, связанных с этими функциями.
Параметр предиката является обычным параметром функции
В аннотации проверки типа вида parameter is Type значение parameter должно принадлежать списку параметров текущей функции.
function missing(param: unknown): prop is number { // Error
return typeof param === "number";
} Это не может быть параметр, связанный с шаблоном деструктуризации, или остаточный параметр:
function destructuring({prop}: {prop: unknown}): prop is number { // Error
return typeof prop === "number";
} function rest(...value: Array<unknown>): value is Array<unknown> { // Error
return Array.isArray(value);
} Тип предиката согласован с типом параметра
Проверка типа Type должна быть совместима с типом параметра. Иными словами, если дано определение
function isT(x: ParamType): x is Type {
return ...
} Flow проверит, что Type является подтипом ParamType. Поэтому следующий пример вызовет ошибку:
function isNumber(x: string): x is number { // Error
return typeof x === "number";
} Функция проверки типа возвращает логическое значение
Функция проверки типа должна возвращать логическое выражение. Следующие объявления недопустимы:
function isNumberNoReturn(x: string): x is string {} // Error function nonMaybe<V extends {...}>(x: ?V): x is V {
return x; // Error
} Корректный вариант nonMaybe выглядит так:
function nonMaybe<V extends {...}>(x: ?V): x is V {
return !!x;
} Тип предиката согласован с уточнённым типом
Помимо перечисленных выше проверок, Flow также проверяет, согласуется ли объявленная проверка типа с проверкой, выполняемой в теле функции. Чтобы обеспечить корректность, Flow должен гарантировать два условия:
- Тип уточнённого параметра в месте возврата после применения предиката из выражения возврата является подтипом типа проверки. Например, следующие определения корректны:
function numOrStr(x: unknown): x is number | string {
return (typeof x === "number" || typeof x === "string");
}
function numOrStrWithException(x: unknown): x is number | string {
if (typeof x === "number") {
return true;
} else {
if (typeof x === "string") {
return true;
} else {
throw new Error("");
}
}
} Однако в следующем примере Flow выдаст ошибки:
function numOrStrError(x: unknown): x is number | string {
return (typeof x === "number" || typeof x === "boolean"); // Error [incompatible-type-guard]
} - Функции проверки типа можно использовать для уточнения ветви else условных выражений. Например:
function isNumber(x: unknown): x is number {
return typeof x === "number";
}
declare const value: number | string;
if (isNumber(value)) {
value as number; // okay
} else {
value as string; // also okay
} Поэтому должно выполняться и обратное условие первого требования. В частности, если отрицать предикат, закодированный в функции, и использовать его для уточнения входных данных, результат не должен пересекаться с типом проверки. Это условие равносильно проверке того, что тип проверки, уточнённый с помощью отрицания предиката функции, является подтипом empty. Например, следующий код вызовет ошибку:
function isPosNum(x: unknown): x is number {
return typeof x === 'number' && x > 0; // Error [incompatible-type-guard]
} Это происходит потому, что отрицание предиката isPosNum — это «x не является числом или x<=0». Этот предикат эквивалентен пустому предикату и не уточняет тип входных данных, к которым применяется.
Если вы столкнулись с ошибками, связанными с этой проверкой, попробуйте использовать одностороннюю проверку типа (запишите implies x is T). Для односторонних проверок типа эта проверка не требуется, поскольку они не уточняют ветвь else условных выражений.
- Параметр, тип которого уточняется, нельзя переназначать в теле функции проверки типа. Поэтому следующие примеры вызовут ошибки:
function isNumberError1(x: unknown): x is number {
x = 1;
return typeof x === "number"; // Error [function-predicate]
} function isNumberError2(x: unknown): x is number { // Error [function-predicate]
function foo() {
x = 1;
}
foo();
return typeof x === "number";
} Замыкание, захватывающее параметр, нарушает корректность проверки только в том случае, если это замыкание действительно вызывается между исходным параметром и return. Определить замыкание, не вызывая его, можно:
function isNumber(x: unknown): x is number {
const reset = () => { x = 1; }; // OK - never invoked
return typeof x === "number";
} Проверки типов с обобщёнными членами объединения
Функции проверки типа могут не пройти проверки согласованности Flow, если свойство члена объединения, по которому выполняется уточнение, использует обобщённый параметр типа. Flow не может доказать, что уточнённый тип совместим с типом проверки, поскольку обобщённый тип может пересекаться с литеральным значением типа проверки:
type Known = {tag: 'known', value: string};
type Dynamic<T> = {tag: T, data: number};
type Item<T> = Known | Dynamic<T>;
function isKnown<T>(x: Item<T>): x is Known {
return x.tag === 'known'; // ERROR
} Ошибка возникает потому, что T может быть 'known', из-за чего невозможно отличить Known от Dynamic<T>, проверяя только tag.
Чтобы исправить это, добавьте отдельное свойство-литерал для различения, не использующее обобщённый тип. Тогда Flow получит конкретную метку для уточнения:
type Known = {type: 'known', tag: 'known', value: string};
type Dynamic<T> = {type: 'dynamic', tag: T, data: number};
type Item<T> = Known | Dynamic<T>;
function isKnown<T>(x: Item<T>): x is Known {
return x.type === 'known';
} Подключение
Чтобы использовать функции проверки типов, необходимо обновить инфраструктуру, чтобы она поддерживала этот синтаксис:
-
prettier: версия 3 или более поздняя с установленным плагином@prettier/plugin-hermes(инструкции см. здесь). Для функций проверки типаthisтребуется Prettier версии 3.5 или более поздней. -
babelсflow-parser/babel-plugin. Инструкции по настройке см. в руководстве по Babel. -
eslintсflow-eslint. Инструкции по настройке см. в руководстве по ESLint.
См. также
-
Уточнения типов — встроенное сужение типов с помощью
typeof,instanceofи проверок на равенство - Объединения — функции проверки типов часто используются для сужения типов объединения
- Функции — синтаксис проверок типов основан на аннотациях типа возвращаемого значения функции
© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/types/type-guards