Spec-Zone.ru › Flow

FAQ

Я проверил, что foo.bar не равен null, но Flow всё равно считает, что это так. Почему это происходит и как это исправить?​

Flow не отслеживает побочные эффекты, поэтому любой вызов функции потенциально может аннулировать вашу проверку. Это называется инвалидацией сужения типов (refinement invalidation). Пример:

type Param = {
  bar: ?string,
}
function myFunc(foo: Param): string {
  if (foo.bar) {
    console.log("checked!");
    return foo.bar; // Error: if you remove the console.log, it works
  }


  return "default string";
}

Вы можете обойти это, сохранив проверенные значения в локальные переменные:

type Param = {
  bar: ?string,
}
function myFunc(foo: Param): string {
  if (foo.bar) {
    const bar = foo.bar;
    console.log("checked!");
    return bar; // Ok!
  }


  return "default string";
}

Я проверил, что значение имеет тип A, так почему Flow всё ещё считает, что это A | B?​

Инвалидация сужения типов также может происходить при обновлении переменных:

type T = string | number;


let x: T = 'hi';


function f() {
  x = 1;
}


if (typeof x === 'string') {
  f();
  x as string; // Error
}

Обходным решением будет объявление переменной как const и рефакторинг кода во избежание повторного присваивания:

type T = string | number;


const x: T = 'hi';


function f(x: T): number {
  return 1;
}


if (typeof x === 'string') {
  const xUpdated = f(x);
  xUpdated as number;
  x as string;
}

Я нахожусь внутри замыкания, и Flow игнорирует проверку if, подтверждающую, что foo.bar определено. Почему?​

В предыдущем разделе мы показали, как результаты сужения теряются после вызова функции. Точно то же самое происходит внутри замыканий, поскольку Flow не отслеживает, как ваше значение может измениться до вызова замыкания:

const people = [{age: 12}, {age: 18}, {age: 24}];
const oldPerson: {age: ?number} = {age: 70};
if (oldPerson.age) {
  people.forEach(person => {
    console.log(`The person is ${person.age} and the old one is ${oldPerson.age}`); // Error
  })
}

Решение здесь состоит в том, чтобы переместить проверку if внутрь forEach или присвоить age промежуточной переменной:

const people = [{age: 12}, {age: 18}, {age: 24}];
const oldPerson: {age: ?number} = {age: 70};
if (oldPerson.age) {
  const age = oldPerson.age;
  people.forEach(person => {
    console.log(`The person is ${person.age} and the old one is ${age}`);
  })
}

Но ведь Flow должен понимать, что эта функция не может инвалидировать это сужение, верно?​

Flow не является полным (complete), поэтому он не может проверять весь код идеально. Вместо этого Flow делает консервативные предположения, пытаясь быть надёжным (sound).

Почему я не могу использовать функцию в условии if для проверки типа свойства?​

Flow не отслеживает сужения типов, выполненные в отдельных вызовах функций:

const add = (first: number, second: number) => first + second;
const val: string | number = 1;
const isNumber = (x: unknown): boolean => typeof x === 'number';
if (isNumber(val)) {
  add(val, 2); // Error
}

Однако вы можете аннотировать свою функцию с помощью type guard, чтобы получить такое поведение:

const add = (first: number, second: number) => first + second;
const val: string | number = 1;
// Return annotation updated:
const isNumber = (x: unknown): x is number => typeof x === 'number';
if (isNumber(val)) {
  add(val, 2);
}

Почему я не могу передать Array<string> в функцию, принимающую Array<string | number>​

Это происходит потому, что изменяемые массивы являются инвариантными по типу — тип элементов должен совпадать в точности, а не просто быть подтипом. См. раздел Вариантность типов для подробного объяснения инвариантности.

Аргумент функции допускает значения string в своём массиве, но в этом случае Flow предотвращает попадание number в исходный массив. Внутри функции вы могли бы добавить number в массив аргумента, из-за чего тип исходного массива перестал бы быть точным.

Вы можете исправить эту ошибку, изменив тип аргумента на ReadonlyArray<string | number>, сделав его ковариантным. Это запрещает телу функции добавлять что-либо в массив, позволяя ему принимать более узкие типы.

Например, это не сработает:

const fn = (arr: Array<string | number>) => {
  arr.push(123); // Oops! Array<string> was passed in - now inaccurate
  return arr;
};


const arr: Array<string> = ['abc'];


fn(arr); // Error!

но с помощью ReadonlyArray вы можете добиться желаемого результата:

const fn = (arr: ReadonlyArray<string | number>) => {
  // arr.push(321); NOTE! Since you are using ReadonlyArray<...> you cannot push anything to it
  return arr;
};


const arr: Array<string> = ['abc'];


fn(arr);

Почему я не могу передать {a: string} в функцию, принимающую {a: string | number}​

Это происходит потому, что изменяемые свойства объектов являются инвариантными по типу — тип свойства должен совпадать в точности, а не просто быть подтипом. См. раздел Вариантность типов для подробного объяснения инвариантности.

Аргумент функции допускает значения string в своём поле, но в данном случае Flow предотвращает запись number в исходный объект. Внутри тела функции вы могли бы изменить объект так, что свойство a получило бы значение number, из-за чего тип исходного объекта перестал бы быть точным.

Вы можете исправить эту ошибку, сделав свойство ковариантным (только для чтения): {readonly a: string | number}. Это запрещает телу функции выполнять запись в свойство, благодаря чему передача более специализированных типов в функцию становится безопасной.

Например, это не сработает:

const fn = (obj: {a: string | number}) => {
  obj.a = 123; // Oops! {a: string} was passed in - now inaccurate
  return obj;
};


const object: {a: string} = {a: 'str' };


fn(object); // Error!

но с ковариантным свойством вы можете добиться желаемого результата:

const fn = (obj: {readonly a: string | number}) => {
  // obj.a = 123 NOTE! Since you are using covariant {readonly a: string | number}, you can't mutate it
  return obj;
};


const object: {a: string} = { a: 'str' };


fn(object);

Почему я не могу сузить объединение объектов?​

На то есть две возможные причины:

  1. Вы используете неточные (inexact) объекты.
  2. Вы деструктурируете объект. При деструктуризации Flow теряет информацию о свойствах объекта.

Неработающий пример:

type Action =
  | {type: 'A', payload: string}
  | {type: 'B', payload: number};


// Not OK
const fn = ({type, payload}: Action) => {
  switch (type) {
    case 'A': return payload.length; // Error!
    case 'B': return payload + 10;
  }
}

Исправленный пример:

type Action =
  | {type: 'A', payload: string}
  | {type: 'B', payload: number};


// OK
const fn = (action: Action) => {
  switch (action.type) {
    case 'A': return action.payload.length;
    case 'B': return action.payload + 10;
  }
}

Я получил ошибку «Missing type annotation». Откуда она берётся?​

Flow требует аннотаций типов на границах модулей, чтобы гарантировать масштабируемость. Чтобы узнать об этом подробнее, ознакомьтесь с нашей статьёй в блоге.

Наиболее распространённый случай, с которым вы столкнётесь, — это экспорт функции или React-компонента. Flow требует аннотировать входные данные. Например, в этом случае Flow выдаст ошибку:

export const add = a => a + 1; // Error

Решение здесь — добавить типы к параметрам функции add:

export const add = (a: number): number => a + 1;

Чтобы узнать, как аннотировать экспортируемые React-компоненты, ознакомьтесь с нашей документацией по HOC.

Есть и другие случаи, когда это происходит, и их может быть сложнее понять. Вы получите ошибку вроде Cannot build a typed interface for this module... Cannot determine the type of this call expression. Please provide an annotation. Например, вы написали следующий код:

const array = ['a', 'b']
export const genericArray = array.map(a => a) // Error

Здесь Flow выдаст предупреждение на export, требуя аннотацию типа. Flow требует аннотировать экспорты, возвращаемые обобщённой (generic) функцией. Метод Array.prototype.map возвращает Array<U>, где U выводится из типа возвращаемого значения функции обратного вызова — этот U является дженериком, выражающим тот факт, что тип функции, переданной в map, связан с типом массива.

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

Вы можете сделать это, аннотировав экспортируемую константу:

const array = ['a', 'b']
export const genericArray: Array<string> = array.map(a => a)

Я перехожу с TypeScript. В чём отличия?​

Flow и TypeScript разделяют большую часть синтаксиса, общую терминологию и большой набор пересекающихся концепций (условные и сопоставленные типы, keyof, as const, unknown, Readonly, дженерики и type guard param is T). Это сближение во многом преднамеренно: синтаксис Flow за последние несколько лет эволюционировал для соответствия TypeScript, поэтому если вы знаете TypeScript, ваша интуиция поможет вам в большинстве ситуаций при работе с программой на Flow.

Там, где они расходятся, это расхождение обычно является осознанным выбором разработчиков Flow в пользу более строгих статических гарантий. Flow отклоняет ряд паттернов, которые TypeScript принимает, но которые могут вызвать ошибки во время выполнения, неявно исказить значения или привести к логическим багам. React — ещё одна заметная область, в которой Flow не копирует TypeScript: Flow предоставляет первоклассный синтаксис для component, hook и renders.

См. руководство Flow для пользователей TypeScript для полного сравнения, разбитого на четыре раздела: что переносится напрямую, общие концепции с разными правилами, концепции только во Flow и возможности только в TypeScript.

См. также​

  • Сужения типов (Refinements) — как работает сужение типов и когда оно инвалидируется
  • Type Guards — пользовательские функции сужения типов для случаев, когда typeof недостаточно
  • Вариантность (Variance) — почему контейнеры подтипов не всегда совместимы
  • Дженерики (Generics) — понимание обобщённых возвращаемых типов и требований к аннотациям
  • Требование к аннотациям (Annotation Requirement) — когда и почему Flow требует явных аннотаций
  • Flow для пользователей TypeScript — полное справочное руководство по сравнению с TypeScript

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

Spec-Zone.ru

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