Головна / Статті / Flow проти TypeScript у 2026 році: перетин синтаксису та більш суворі перевірки

Flow проти TypeScript у 2026 році: перетин синтаксису та більш суворі перевірки

Дізнайтеся, як синтаксис Flow тепер відповідає синтаксису TypeScript, а також про його унікальні вирази зі співставленням, типи, специфічні для React, та проблеми під час виконання, які залишає непоміченими TypeScript, але виявляє Flow.

1178 слів

До 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, проте все одно зазнає невдачі під час виконання.

    1. Вилучення методу з екземпляра призводить до втрати його прив’язки this у TypeScript
    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.

    1. 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 за замовчуванням є точними, тобто додаткові властивості відхиляються незалежно від того, яким чином значення потрапляє до своєго місця призначення.

    1. 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> повністю усуває ризик, адже імутабельність робить таке розширення безневинним.

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

    Пов’язана література