Inicio / Artículos / Flow frente a TypeScript en 2026: superposición de sintaxis y verificaciones más estrictas

Flow frente a TypeScript en 2026: superposición de sintaxis y verificaciones más estrictas

Explore cómo la sintaxis de Flow ahora refleja la de TypeScript, además de sus expresiones únicas de coincidencia, tipos específicos para React y errores en tiempo de ejecución que TypeScript pasa por alto pero Flow detecta.

1178 palabras

Hasta 2026, Flow ha evolucionado siguiendo tres líneas destacadas:

  • Su sintaxis ahora coincide en gran medida con la de TypeScript: los desarrolladores que ya conocen TypeScript reconocerán la mayor parte de lo que hace Flow.
  • Ofrece ciertas capacidades que TypeScript aún carece: lo más notable son las expresiones match y las estructuras dedicadas component, hook y renders para React.
  • Cuando los dos sistemas de tipos difieren, Flow tiende a elegir la opción más estricta: marca aquellos patrones que TypeScript permite pero que pueden causar problemas en tiempo de ejecución, dañar datos silenciosamente o introducir errores lógicos sutiles.

La sintaxis de Flow y TypeScript se han convergido

Mira el fragmento a continuación: ¿puedes determinar si es de Flow o de TypeScript? Probablemente no.

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

Cualquiera que esté familiarizado con TypeScript no encontrará nada sorprendente aquí. El ejemplo utiliza keyof, campos readonly, el tipo unknown, acceso indexado a través de T[K], y genericos limitados con extends. Flow también admite tipos condicionales, tipos mapeados, protectores de tipo y as const, reflejando de cerca la herramienta de TypeScript.

El resto de este texto se centra en las áreas donde Flow va más allá que TypeScript.

Funcionalidades exclusivas de Flow

Aquí se presentan capacidades que existen en Flow pero no tienen equivalente en TypeScript.

Expresiones y sentencias match

Flow ofrece una construcción match para la comparación de patrones como expresión. El compilador la verifica exhaustivamente y permite desestructurar valores directamente como parte del proceso de comparación. Si olvida manejar un caso —por ejemplo, type: 'remove'match lo señalará y le indicará exactamente qué falta.

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

También existe una forma de declaración para match, que se comporta como un switch del cual no es posible pasar a otro caso, y cuenta con todas las mismas funcionalidades que la versión de expresión.

React: component, hook, renders

  • Sintaxis de component: trata a los componentes de React como un concepto integrado a nivel del lenguaje. Las propiedades se declaran directamente como parámetros con nombre, y el verificador de tipos puede aplicar reglas de corrección específicas para React.
  • types renders: le permiten describir cómo se componen los componentes entre sí. Los sistemas de diseño y las bibliotecas de componentes pueden especificar con exactitud qué se puede aceptar en un slot y qué puede producir un componente, y el verificador aplica esos acuerdos incluso a través de componentes envolventes.
  • Sintaxis hook: marca a los hooks como un tipo distinto, separado de las funciones ordinarias. Flow integra directamente en su verificador de tipos las Reglas de React, detectando llamadas condicionales a hooks, mezclar hooks con funciones regulares y mutaciones inseguras del valor devuelto por un hook durante el renderizado, todo ello sin necesidad de un plugin separado de 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
    

    Cuatro caídas en tiempo de ejecución que TypeScript no detecta, pero Flow sí

    Aquí es donde los dos sistemas de tipos divergen realmente. Cada ejemplo a continuación pasa la verificación de tipos en TypeScript 6.0.3 con el modo strict activado, pero aún así falla en tiempo de ejecución.

    1. Al extraer un método de una instancia, se pierde su enlace this en 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`
    

    Una vez que se extrae counter.increment del objeto counter, ya no está vinculado a él; por lo tanto, al llamarlo posteriormente como tick(), this tiene el valor undefined, lo que provoca un error en ++this.count. TypeScript trata al método extraído como una función ordinaria y permite su ejecución sin problemas. En contraste, Flow rechaza la extracción justo en el momento en que se pierde el enlace this.

    1. TypeScript permite que propiedades adicionales se introduzcan mediante asignaciones indirectas
    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
    );
    

    Técnicamente, los tipos de objeto de TypeScript permiten propiedades adicionales. La “verificación de propiedad excesiva” que normalmente marcaría algo como {apple: 1.5, sample: "free"} solo se activa cuando se asigna un literal de objeto directamente. Si se pasa el mismo valor a través de una variable intermedia, esa verificación ya no aplica; por lo tanto, el campo adicional sample pasa desapercibido. Los tipos de objeto de Flow son estrictos por defecto, lo que significa que las propiedades extra son rechazadas sin importar cómo llegue el valor a su destino.

    1. TypeScript permite que un tipo más amplio sea insertado en un array mutable más restringido
    // 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
    

    Dado que TypeScript trata a los arrays mutables como covariantes, considera que Array<string> es un subtipo de Array<string | Error>. Esto significa que la llamada es aceptada, y la llamada a push dentro de appendError termina insertando un Error en lo que el llamante creía que era un array exclusivamente de string. Flow evita esto al tratar a los arrays mutables como invariantes, bloqueando la ampliación directamente en el lugar de la llamada. Si la función no necesita realmente modificar su entrada, cambiar el parámetro a ReadonlyArray<string | Error> elimina por completo el riesgo, ya que la inmutabilidad hace que la ampliación no cause problemas.

    1. TypeScript no verifica qué hace realmente el cuerpo de un guardián de tipo
    // 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 solo verifica la firma declarada de un predicado de tipo; nunca examina qué devuelve realmente el cuerpo de la función. Flow, en cambio, verifica ambas direcciones: cada instrucción return debe reducir efectivamente el tipo al tipo objetivo del guardia, y la rama else debe excluir correctamente ese tipo. Como resultado, Flow rechaza los predicados cuya lógica no coincide con lo que afirman verificar.

    Lea la comparación completa

    Para un conjunto más amplio de ejemplos, el sitio de documentación oficial de Flow ofrece un análisis detallado que compara los dos lenguajes punto por punto, abarcando más de veinte casos adicionales además de los mostrados aquí: vea la guía de comparación.

    Lecturas relacionadas