Flow против TypeScript в 2026 году: совпадения синтаксиса и более строгая проверка
Узнайте, как синтаксис Flow теперь соответствует синтаксису TypeScript, а также о его уникальных выражениях сравнения, типах, специфичных для React, и ошибках во время выполнения, которые TypeScript упускает, но Flow обнаруживает.
К 2026 году Flow развился по трем основным направлениям:
- Его синтаксис теперь во многом совпадает со синтаксисом TypeScript: разработчики, уже знакомые с TypeScript, смогут понять большинство особенностей Flow.
- Он предоставляет некоторые функции, которых пока нет в TypeScript: в частности, выражения
matchи специальные конструкцииcomponent,hookиrendersдля React. - Когда синтаксисы двух систем различаются, Flow обычно выбирает более строгий вариант: он выделяет шаблоны, которые разрешены TypeScript, но могут привести к сбоям во время выполнения, к тайному повреждению данных или к скрытым логическим ошибкам.
Синтаксис Flow и TypeScript сблизились
Посмотрите на приведённый ниже фрагмент — сможете ли вы определить, это Flow или TypeScript? Скорее всего, нет.
type User = {
readonly name: string,
readonly age: number,
readonly metadata: unknown,
};
function get<K extends keyof User>(user: User, key: K): User[K] {
return user[key];
}
declare const user: User;
const age: number = get(user, 'age');
Тем, кто знаком с TypeScript, здесь не будет ничего удивительного. Пример использует оператор keyof, поля с модификатором readonly, тип unknown, индексированный доступ через T[K] и генерики с ограничениями с помощью extends. Flow также поддерживает условные типы, преобразованные типы, типовые защитники и оператор as const, тесно повторяя набор инструментов TypeScript.
В оставшейся части статьи рассматриваются аспекты, в которых Flow превосходит TypeScript.
Функции, доступные только в Flow
Здесь перечислены возможности, существующие в Flow, но отсутствующие в TypeScript.
Выражения и операторы match
Flow предоставляет конструкцию match для сопоставления шаблонов в виде выражения. Компилятор тщательно проверяет её и позволяет напрямую разбирать значения в процессе сопоставления. Если вы забудете обработать какой-либо случай — например, type: 'remove' — match указает на это и сообщает, что именно отсутствует.
type Action =
| {type: 'add', text: string}
| {type: 'toggle', id: string}
| {type: 'remove', id: string};
declare const action: Action;
const description = match (action) { // ERROR: 'remove' case missing
{type: 'add', const text} => `Add: ${text}`,
{type: 'toggle', const id} => `Toggle ${id}`,
};
Существует также форма оператора match в виде инструкции, которая ведёт себя как switch, не допускающий перехода к следующему случаю, и обладает всеми теми же возможностями, что и версия в виде выражения.
React: component, hook, renders
- Синтаксис
component: рассматривает компоненты React как встроенное понятие на уровне языка. Параметры объявляются напрямую в виде именованных аргументов, а проверщик типов может обеспечивать соблюдение правил корректности, характерных для React.
renders типы: позволяют описать, как компоненты взаимодействуют друг с другом. Системы дизайна и библиотеки компонентов могут точно указать, что может приниматься в слот и что может генерироваться компонентом, причем проверщик соблюдает эти условия даже в случае использования оберточных компонентов.hook синтаксис: выделяет хуки в отдельную категорию, отличную от обычных функций. Flow встраивает правила React непосредственно в свой проверщик типов, выявляя условные вызовы хуков, смешивание хуков с обычными функциями и небезопасные изменения значения, возвращаемого хуком во время отрисовки — всё это без необходимости использования отдельного плагина ESLint.component Header(text: string, color: string) {
return <div style={{color}}>{text}</div>;
}
component MainHeader(text: string) renders Header {
return <Header text={text} color="red" />;
}
component Layout(header: renders Header) {
return <div>
{header}
<section>Content</section>
</div>;
}
const ok = <Layout header={<MainHeader text="Flow" />} />;
const bad = <Layout header={<footer />} />; // ERROR
Четыре сбоя во время выполнения, которые TypeScript не обнаруживает — но Flow обнаруживает
Именно здесь две системы типов действительно различаются. Все приведённые ниже примеры проходят проверку типов в TypeScript 6.0.3 с включённым режимом strict, но всё равно терпят неудачу во время выполнения.
- При извлечении метода у экземпляра в TypeScript теряется связь с
this
class Counter {
count: number = 0;
increment(): number {
return ++this.count;
}
}
const counter = new Counter();
const tick = counter.increment; // TS accepts. Flow rejects.
tick(); // Runtime crash! `this` is undefined inside `increment`
Как только метод counter.increment извлекается у объекта counter, он больше не связан с ним — поэтому его последующее вызование под именем tick() происходит с this, равным undefined, в результате чего возникает ошибка при выполнении ++this.count. TypeScript рассматривает извлечённый метод как обычную функцию и позволяет его вызову без замечаний. Flow, в отличие от него, отклоняет такое извлечение именно в тот момент, когда теряется связь с this.
- TypeScript позволяет дополнительным свойствам проникнуть через косвенное присваивание
type Prices = {apple: number, banana: number};
const items = {apple: 1.5, banana: 0.5, sample: "free"};
const prices: Prices = items; // TS accepts. Flow rejects.
Object.values(prices).map(
price => price.toFixed(2), // Runtime crash! `sample` isn't a number
);
Типы объектов в TypeScript технически позволяют наличие дополнительных свойств. «Проверка на избыточные свойства», которая обычно выявляла бы что-то вроде {apple: 1.5, sample: "free"}, активируется только тогда, когда значение присваивается напрямую в виде литерала объекта. Если передать то же значение через промежуточную переменную, эта проверка больше не применяется — поэтому лишнее поле sample проходит незамеченным. Типы объектов в Flow по умолчанию являются строгими, что означает отклонение дополнительных свойств независимо от способа их передачи.
- В TypeScript разрешается помещение более широкого типа в более узкий изменяемый массив
// TypeScript: accepted.
function appendError(errs: Array<string | Error>) {
errs.push(new Error("oops"));
}
const errors: Array<string> = [];
appendError(errors); // TS accepts. Flow rejects.
errors[0].toUpperCase(); // Runtime crash! `errors[0]` isn't a string
Поскольку TypeScript рассматривает изменяемые массивы как ковариантные, он считает Array<string> подтипом Array<string | Error>. Это означает, что вызов принимается, и операция push внутри функции appendError приводит к добавлению элемента типа Error в массив, который вызывающий считал состоящим исключительно из строк. Flow избегает этой проблемы, рассматривая изменяемые массивы как инвариантные и блокируя процесс расширения типов прямо на месте вызова. Если у функции нет реальной необходимости изменять входные данные, замена параметра на ReadonlyArray<string | Error> полностью устраняет риск, поскольку неизменяемость делает такое расширение безвредным.
- TypeScript не проверяет, что на самом деле делает тело функции-защитника типов
// TypeScript: accepted, but this body is true for numbers, not strings.
function isString(x: unknown): x is string {
return typeof x === "number"; // TS accepts. Flow rejects.
}
const data: unknown = 1;
if (isString(data)) {
data.toUpperCase(); // Runtime crash! `data` isn't a string
}
TypeScript проверяет только заявленную сигнатуру типового предиката — он никогда не анализирует, что на самом деле возвращает тело функции. Flow проверяет оба направления: каждое операторное выражение return должно действительно приводить к целевому типу, указанному в условии, а ветка else должна корректно исключить этот тип. В результате Flow отклоняет предикаты, логика которых не соответствует тому, что они должны проверять.
Читайте полное сравнение
Для более широкого набора примеров на сайте документации Flow представлен подробный обзор, сравнивающий оба языка пункт за пунктом; в нем рассматривается более двадцати дополнительных случаев помимо тех, что показаны здесь: посмотрите руководство по сравнению.
Связанная литература
- Стандарты полноценного стека JavaScript на 2026 год: TypeScript, RSC и другие технологии — Объясняется, почему TypeScript, React Server Components и более эффективные методы управления состоянием стали стандартным инструментарием для команд, работающих с JavaScript, в 2026 году.
- Предложения TC39 в 2026 году: декораторы, Temporal и Signals — подробное объяснение — Практический обзор трех предложений TC39: встроенные декораторы, API Temporal и Signals, а также их влияние на разработчиков полноценного стека JavaScript и TypeScript.