Spec-Zone.ru › Flow

Функции проверки типов

Функции проверки типов — это функции, тип возвращаемого значения которых аннотирован как param is PredicateType; при возврате true они сужают тип параметра.

function predicate(param: InputType): param is PredicateType {
  return <some_expression>;
}

Тип этой функции также можно записать в виде аннотации типа:

type PredicateFunc = (param: InputType) => param is PredicateType;
Сравнение с TypeScript

В отличие от 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 должен гарантировать два условия:

  1. Тип уточнённого параметра в месте возврата после применения предиката из выражения возврата является подтипом типа проверки. Например, следующие определения корректны:
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]
}
  1. Функции проверки типа можно использовать для уточнения ветви 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 условных выражений.

  1. Параметр, тип которого уточняется, нельзя переназначать в теле функции проверки типа. Поэтому следующие примеры вызовут ошибки:
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

Spec-Zone.ru

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