Flow kontra TypeScript w 2026 roku: pokrywanie się składni i bardziej ścisłe kontrole
Odkryj, w jaki sposób składnia Flow teraz odpowiada składni TypeScript, a także jego unikalne wyrażenia match, typy specyficzne dla React oraz błędy w czasie wykonywania, których TypeScript nie dostrzega, ale Flow je wykrywa.
Do 2026 roku Flow rozwinął się w trzech istotnych kierunkach:
- Jego składnia w dużej mierze pokrywa się teraz z składnią TypeScript: programiści, którzy już znają TypeScript, będą rozpoznawać większość funkcji oferowanych przez Flow.
- Zapewnia pewne możliwości, których nadal brakuje w TypeScript: najważniejsze z nich to wyrażenia
matchoraz dedykowane konstrukcjecomponent,hookirendersdla React. - Gdy oba systemy typów się różnią, Flow zwykle wybiera bardziej rygorystyczną opcję: sygnalizuje wzorce, które TypeScript dopuszcza, ale które mogą powodować problemy w czasie wykonywania, potajemnie uszkadzać dane lub wprowadzać subtelne błędy logiczne.
Składnia Flow i TypeScript zbiegła się
Rozważ poniższy fragment — czy potrafisz określić, czy to Flow, czy TypeScript? Prawdopodobnie nie.
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');
Każdy, kto dobrze zna TypeScript, nie znajdzie tu nic zaskakującego. Przykład wykorzystuje keyof, pola readonly, typ unknown, dostęp indeksowany za pomocą T[K] oraz generyki z ograniczeniami przy użyciu extends. Flow obsługuje również typy warunkowe, typy mapowane, strażniki typów oraz as const, w dużej mierze odzwierciedlając zestaw narzędzi TypeScript.
Pozostała część tego tekstu koncentruje się na obszarach, w których Flow idzie dalej niż TypeScript.
Funkcje dostępne tylko w Flow
Oto funkcjonalności, które istnieją w Flow, ale nie mają odpowiedników w TypeScript.
Wyrażenia i instrukcje match
Flow oferuje konstrukcję match do porównywania wzorów w postaci wyrażenia. Kompilator sprawdza ją w sposób wyczerpujący i umożliwia bezpośrednie rozbieranie wartości w ramach procesu porównywania. Jeśli zapomnisz obsłużyć dany przypadek — na przykład type: 'remove' — match wskazaje na to i poinformuje cię dokładnie, czego brakuje.
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}`,
};
Istnieje również forma instrukcji match, która zachowuje się jak switch i nie pozwala na przechodzenie do kolejnych przypadek, przy czym posiada wszystkie te same funkcje co wersja wyrażeniowa.
React: component, hook, renders
- Syntaksa
component: traktuje komponenty React jako wbudowany pojęcie na poziomie języka. Atrybuty są deklarowane bezpośrednio jako parametry o nazwach, a sprawdzacz typów może egzekwować reguły poprawności specyficzne dla React.
renders typy: umożliwiają opisanie sposobu, w jaki komponenty się ze sobą łączą. Systemy projektowe i biblioteki komponentów mogą dokładnie określić, co slot może przyjąć oraz co komponent może wygenerować, a narzędzie sprawdzające egzekwuje te zasady nawet w przypadku komponentów otaczających.hook składnia: oznacza hooki jako osobny, odrębny typ, oddzielony od zwykłych funkcji. Flow włącza zasady React bezpośrednio do swojego narzędzia sprawdzającego typów, wykrywając warunkowe wywołania hooków, mieszanie hooki z zwykłymi funkcjami oraz niebezpieczną modyfikację wartości zwracanej przez hook podczas renderowania — wszystko to bez konieczności używania oddzielnego pluginu 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
Cztery awarie w czasie wykonywania, których TypeScript nie wykrywa — ale Flow tak
Tutaj dwa systemy typów naprawdę się różnią. Każdy z poniższych przykładów przechodzi sprawdzenie typów w TypeScript 6.0.3 z włączonym trybem strict, a mimo to zawodzi podczas wykonywania.
- Wyjęcie metody z instancji powoduje utratę powiązania
thisw 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`
Gdy usuniesz counter.increment z obiektu counter, nie jest on już z nim powiązany — dlatego późniejsze wywołanie go jako tick() odbywa się przy this równym undefined, co powoduje błąd w ++this.count. TypeScript traktuje wyjętą metodę jak zwykłą funkcję i pozwala na jej wywołanie bez żadnych zastrzeżeń. Flow natomiast odrzuca takie wyjęcie dokładnie w momencie utraty powiązania this.
- TypeScript pozwala na dodanie dodatkowych właściwości poprzez pośrednie przypisanie
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
);
Typy obiektów w TypeScript technicznie pozwalają na dodatkowe właściwości. „Sprawdzenie nadmiarowych właściwości”, które zwykle wykryłoby coś takiego jak {apple: 1.5, sample: "free"}, aktywuje się tylko wtedy, gdy przypisujemy bezpośrednio literał obiektu. Jeśli ta sama wartość jest przekazywana przez zmienną pośrednią, ta kontrola już nie obowiązuje — w rezultacie niechciana właściwość sample przechodzi bez zauważenia. Typy obiektów w Flow są domyślnie dokładne, co oznacza, że dodatkowe właściwości są odrzucane bez względu na to, w jaki sposób wartość trafia do celu.
- TypeScript pozwala na umieszczenie szerszego typu w węższym, zmiennej tablicy
// 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
Ponieważ TypeScript traktuje modyfikowalne tablice jako kowariantne, uważa Array<string> za podtyp Array<string | Error>. Oznacza to, że taka wywołanie jest akceptowane, a operacja push w funkcji appendError powoduje dodanie elementu typu Error do tablicy, o której użytkownik sądził, że zawiera wyłącznie wartości typu string. Flow unika tego, traktując modyfikowalne tablice jako niezmiennicze i blokując rozszerzenie typów w miejscu wywołania. Jeśli funkcja rzeczywiście nie potrzebuje modyfikowania swojego wejścia, zmiana parametru na ReadonlyArray<string | Error> całkowicie eliminuje to ryzyko, ponieważ niezmienniczość sprawia, że rozszerzenie typów nie stanowi problemu.
- TypeScript nie sprawdza, co faktycznie robi ciało funkcji typu guard
// 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 sprawdza jedynie deklarowaną specyfikację predykatu typu — nigdy nie analizuje tego, co faktycznie zwraca ciało funkcji. Flow sprawdza obie strony: każde zdanie return musi rzeczywiście doprowadzić do typu docelowego warunku, a gałąź else musi prawidłowo wykluczyć ten typ. W rezultacie Flow odrzuca predykaty, których logika nie odpowiada temu, co deklarują jako sprawdzane.
Czytaj pełne porównanie
Dla szerszego zestawu przykładów strona dokumentacyjna Flow zawiera obszerny tekst porównujący oba języki punkt po punkcie, obejmujący ponad dwadzieścia dodatkowych przypadków poza tymi pokazanymi tutaj: zobacz przewodnik porównawczy.
Powiązane materiały
- Standardy JavaScript Full-Stack w 2026 roku: TypeScript, RSC i inne — Wyjaśnia, dlaczego TypeScript, React Server Components oraz bardziej efektywne podejścia do zarządzania stanem stały się standardowym zestawem narzędzi dla zespołów pracujących z JavaScript w 2026 roku.
- Propozycje TC39 w 2026 roku: Dekoratory, Temporal i Signals wyjaśnione — Praktyczny przegląd trzech propozycji TC39 – wbudowanych dekoratorów, API Temporal oraz mechanizmu Signals – i tego, co one oznaczają dla programistów JavaScript Full-Stack oraz TypeScript.