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); Почему я не могу сузить объединение объектов?
На то есть две возможные причины:
- Вы используете неточные (inexact) объекты.
- Вы деструктурируете объект. При деструктуризации 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