Accueil / Articles / Flow contre TypeScript en 2026 : chevauchement de la syntaxe et contrôles plus stricts

Flow contre TypeScript en 2026 : chevauchement de la syntaxe et contrôles plus stricts

Découvrez comment la syntaxe de Flow reflète désormais celle de TypeScript, ainsi que ses expressions de correspondance uniques, les types spécifiques à React, et les erreurs en temps de exécution que TypeScript rate mais que Flow détecte.

1178 mots

D’ici 2026, Flow a évolué selon trois axes majeurs :

  • Sa syntaxe coïncide désormais en grande partie avec celle de TypeScript : les développeurs déjà familiers avec TypeScript reconnaîtront la plupart des fonctionnalités de Flow.
  • Il offre certaines capacités que TypeScript ne possède pas encore : notamment les expressions match ainsi que des constructions dédiées component, hook et renders pour React.
  • Lorsque les deux systèmes de types sont en désaccord, Flow a tendance à choisir l’option la plus stricte : il signale les schémas que TypeScript autorise mais qui peuvent provoquer des problèmes en temps de exécution, corrompre silencieusement les données ou introduire des erreurs logiques subtiles.

La syntaxe de Flow et TypeScript s’est convergée

Jetez un coup d’œil au fragment ci-dessous — pouvez-vous dire s’il s’agit de Flow ou de TypeScript ? Probablement pas.

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');

Tout utilisateur familier avec TypeScript ne trouvera rien de surprenant ici. L’exemple fait appel à keyof, aux champs readonly, au type unknown, à l’accès indexé via T[K], ainsi qu’aux génériques limités avec extends. Flow prend également en charge les types conditionnels, les types mappés, les gardes de type et as const, reproduisant ainsi fidèlement l’ensemble des outils de TypeScript.

Le reste de cet article se concentre sur les domaines où Flow va plus loin que TypeScript.

Fonctionnalités exclusives à Flow

Voici les capacités présentes dans Flow mais qui n’ont pas d’équivalent en TypeScript.

Expressions et instructions match

Flow propose une construction match pour la correspondance de motifs en tant qu’expression. Le compilateur la vérifie de manière exhaustive et vous permet de déstructurer les valeurs directement dans le cadre du processus de correspondance. Si vous oubliez de gérer un cas — par exemple type: 'remove'match vous le signalera et vous indiquera précisément ce qui manque.

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}`,
};

Il existe également une forme déclarative de match, qui se comporte comme un switch ne permettant pas de sauter de cas et qui dispose des mêmes fonctionnalités que la version expression.

React : component, hook, renders

  • Syntaxe component : traite les composants React comme un concept intégré au niveau du langage. Les props sont déclarés directement en tant que paramètres nommés, et le vérificateur de types peut appliquer des règles de correction spécifiques à React.
  • types renders : vous permettent de décrire la manière dont les composants s’assemblent entre eux. Les systèmes de conception et les bibliothèques de composants peuvent spécifier précisément ce qu’un slot est autorisé à accepter et ce qu’un composant est autorisé à produire, et le vérificateur applique ces contrats même à travers des composants d’emballage.
  • Syntaxe hook : identifie les hooks comme une catégorie distincte, séparée des fonctions ordinaires. Flow intègre directement les Règles de React dans son vérificateur de types, détectant les appels conditionnels aux hooks, le mélange des hooks avec des fonctions régulières, ainsi que les modifications dangereuses de la valeur retournée par un hook lors du rendu — tout cela sans avoir besoin d’un plugin ESLint distinct.
  • 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
    

    Quatre plantages en temps de exécution que TypeScript ne détecte pas — mais Flow si

    C’est ici que les deux systèmes de types divergent réellement. Chaque exemple ci-dessous passe le contrôle de type sous TypeScript 6.0.3 avec le mode strict activé, mais échoue néanmoins en temps de exécution.

    1. Lorsqu’on extrait une méthode d’une instance, son lien avec this est perdu dans 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`
    

    Dès que l’on extrait counter.increment de l’objet counter, celle-ci n’est plus liée à lui — donc lorsqu’on l’appelle ensuite sous le nom de tick(), this vaut undefined, ce qui provoque une erreur avec ++this.count. TypeScript traite la méthode extraite comme une fonction ordinaire et autorise l’appel sans problème. En revanche, Flow rejette cette extraction au moment précis où le lien avec this est perdu.

    1. TypeScript permet à des propriétés supplémentaires d’entrer en douce par le biais d’une affectation indirecte
    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
    );
    

    Les types d’objets de TypeScript permettent techniquement l’ajout de propriétés supplémentaires. La « vérification des propriétés excédentaires » qui signalerait normalement quelque chose comme {apple: 1.5, sample: "free"} ne s’active que lorsque l’on assigne directement un littéral d’objet. En faisant passer la même valeur par une variable intermédiaire, cette vérification n’est plus appliquée — ce qui permet au champ sample non désiré de passer inaperçu. Les types d’objets de Flow sont par défaut stricts, ce qui signifie que les propriétés supplémentaires sont rejetées quel que soit le moyen par lequel la valeur atteint sa destination.

    1. TypeScript permet d’insérer un type plus large dans un tableau mutable plus restreint
    // 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
    

    Puisque TypeScript considère les tableaux modifiables comme covariants, il traite Array<string> comme un sous-type de Array<string | Error>. Cela signifie que l’appel est accepté, et l’appel à push à l’intérieur de appendError finit par insérer un élément de type Error dans ce qui était perçu par l’appelant comme un tableau ne contenant que des string. Flow évite cela en traitant les tableaux modifiables comme invariants, bloquant ainsi ce type d’élargissement directement au niveau de l’appel. Si la fonction n’a pas vraiment besoin de modifier son entrée, passer le paramètre en ReadonlyArray<string | Error> élimine complètement ce risque, car l’immutabilité rend cet élargissement inoffensif.

    1. TypeScript ne vérifie pas ce que fait réellement le corps d’un type 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 ne vérifie que la signature déclarée d’un prédicat de type — il n’examine jamais ce que le corps de la fonction renvoie réellement. Flow, quant à lui, vérifie les deux directions : chaque instruction return doit véritablement restreindre le type au type cible du prédicat, et la branche else doit correctement exclure ce type. Par conséquent, Flow rejette les prédicats dont la logique ne correspond pas à ce qu’ils prétendent vérifier.

    Lire la comparaison complète

    Pour un ensemble plus large d’exemples, le site de documentation officiel de Flow propose une analyse détaillée comparant les deux langages point par point, couvrant plus de vingt cas supplémentaires par rapport à ce qui est présenté ici : consultez le guide de comparaison.

    Lectures complémentaires