Angular y React: dos soluciones opuestas al mismo problema

Leonardo Gurgitano7 minLeer en inglés

Pilares reflejados en agua oscura: la misma forma repetida al revés.
Pilares reflejados en agua oscura: la misma forma repetida al revés.

Casi toda comparación entre Angular y React sigue describiendo dos herramientas que ya no existen. Angular 21 salió en noviembre de 2025 y cambió su mecanismo central. React estabilizó su compilador a fines de ese mismo año. Los dos resolvieron el mismo problema, y llama la atención que las soluciones sean inversas entre sí.

Vale la pena entender qué arreglaron, porque de ahí sale un criterio para elegir mucho mejor que la lista de características.

El problema que compartían

Cuando algo cambia en una aplicación —el usuario escribe, llega una respuesta del servidor—, hay que actualizar la parte de la pantalla que corresponde. La pregunta difícil es cómo sabe la herramienta qué parte cambió.

Durante casi una década, ninguna de las dos lo sabía con precisión. Las dos hacían de más, por motivos distintos.

Cómo lo resolvía Angular: revisar todo

Angular usaba una biblioteca llamada zone.js, que interceptaba todas las operaciones asincrónicas del navegador: temporizadores, eventos, peticiones de red. Cada vez que ocurría una, Angular recorría el árbol completo de componentes comparando valores para ver cuáles habían cambiado.

Funcionaba, y tenía una ventaja real: el programador no declaraba nada. Asignaba un valor a una propiedad y la pantalla se actualizaba.

El costo era doble. En aplicaciones grandes, revisar cientos de componentes ante cada clic se nota. Y cuando algo se actualizaba de más —o no se actualizaba—, entender por qué era difícil, porque la causa estaba en una biblioteca que modificaba el comportamiento del navegador desde afuera.

Cómo lo resolvía React: volver a ejecutar todo

React tomó el camino contrario. Cuando el estado de un componente cambia, React vuelve a ejecutar esa función de componente y la de todos sus hijos, produce una nueva descripción de la interfaz, la compara con la anterior y aplica únicamente las diferencias al documento.

El modelo es más fácil de razonar: dado un estado, la interfaz es una función de ese estado. Pero volver a ejecutar el subárbol entero en cada cambio también es trabajo de más, y la solución quedaba a cargo del programador:

// Marcar a mano qué no debe recalcularse
const ordenados = useMemo(() => ordenar(items), [items]);
const alHacerClic = useCallback(() => seleccionar(id), [id]);
export default memo(Fila);

Ese código no expresa ninguna decisión de producto: es instrumental para evitar trabajo innecesario. Y es fácil equivocarse. Una lista de dependencias incompleta produce datos viejos en pantalla; una de más anula el beneficio. Es una fuente conocida de errores difíciles de encontrar.

Las dos soluciones

Acá es donde cada uno eligió la dirección opuesta.

Angular hizo la reactividad explícita

Angular 21 pasó a señales (signals) y desactivó zone.js: el modo zoneless es el valor por defecto en proyectos nuevos.

Una señal es un valor que sabe quién lo está leyendo. Cuando cambia, avisa exactamente a esos lugares. Ya no hay que revisar el árbol: la dependencia está registrada.

cantidad = signal(0);
precio = signal(100);
// Se recalcula solo cuando cambia alguna de las dos, y avisa a quien la lea
total = computed(() => this.cantidad() * this.precio());

Los paréntesis al leer el valor no son un detalle de sintaxis: son lo que permite registrar la lectura. La dependencia queda escrita en el código.

Angular 21 trae además Signal Forms, que lleva ese mismo modelo a los formularios, aunque todavía es experimental. También reemplaza Karma por Vitest para las pruebas. Lo demás de la versión ya es estable.

React lo dejó igual y agregó un compilador

React no cambió su modelo. Lo que hizo fue eliminar el trabajo manual: el React Compiler alcanzó la versión 1.0 y es estable desde fines de 2025.

Analiza los componentes en tiempo de compilación, deduce qué se puede reutilizar entre ejecuciones e inserta esa optimización automáticamente. El código que escribís vuelve a ser el simple:

// Sin useMemo, sin useCallback, sin memo. El compilador lo resuelve.
function Tabla({ items, id }) {
  const ordenados = ordenar(items);
  const alHacerClic = () => seleccionar(id);
  return <Filas datos={ordenados} onClick={alHacerClic} />;
}

La misma meta, una diferencia que importa

Los dos llegaron a que la aplicación solo recalcule lo que cambió. Pero resolvieron quién se hace cargo de eso:

Angular 21 React 19 + compilador
Dónde vive la optimización En el código que escribís En el paso de compilación
Qué ves al leer Las dependencias, explícitas Código sin instrumentación
Qué hay que aprender Un modelo nuevo de reactividad Nada nuevo; se quita código
De qué depende que funcione De declararlo bien De que el compilador entienda tu código

Ese último punto es el que se discute menos. El compilador de React necesita que los componentes cumplan ciertas reglas —no mutar valores recibidos, no producir efectos durante el renderizado— para poder razonar sobre ellos. Cuando el código las cumple, funciona sin que te enteres. Cuando no, el compilador se abstiene de optimizar esa parte, y el motivo no está a la vista en el archivo.

Angular tiene el problema inverso: nada es implícito, pero hay que escribirlo. Y migrar una aplicación existente a señales es trabajo real, no una actualización de versión.

Lo que sí los sigue diferenciando

Resuelto el mecanismo, las diferencias de fondo son las mismas que hace años, y son las que deberían decidir la elección.

Angular incluye todo. Enrutador, formularios, cliente HTTP, inyección de dependencias, herramientas de prueba: viene incluido, mantenido por el mismo equipo y con una única forma de hacer cada cosa. Además, ng update migra el código automáticamente entre versiones mayores, incluidos los cambios que rompen compatibilidad.

Eso importa más de lo que parece cuando hay varios equipos tocando la misma aplicación durante años, o cuando la rotación de gente es alta: quien entra encuentra una estructura conocida en lugar de las decisiones particulares del equipo anterior.

React es una biblioteca. Resuelve la interfaz y nada más. El enrutador, el manejo de datos, los formularios y las pruebas los elegís vos. Eso permite armar exactamente lo que tu proyecto necesita, y obliga a decidir y a mantener esas decisiones.

A cambio, el ecosistema es enorme: para cualquier problema hay varias soluciones maduras, y encontrar gente con experiencia es más fácil.

Sobre los Server Components, que se anuncian como el futuro de React desde hace dos años, conviene mirar los números: están en el 45 % de los proyectos nuevos, un 6 % de los desarrolladores los menciona como una dificultad, y las aplicaciones de una sola página siguen siendo el 84,5 % del total. Son estables y sirven, pero no reemplazaron nada todavía.

Cómo elegiría

Angular, si la aplicación va a vivir muchos años con varios equipos alrededor, si valorás que las decisiones estructurales vengan tomadas, o si el equipo viene de lenguajes con tipos y objetos —C#, Java— donde la inyección de dependencias y las clases son terreno conocido.

React, si querés control sobre cada pieza, si el proyecto tiene requisitos que no encajan en una estructura fija, o si necesitás contratar rápido.

Y una advertencia sobre el criterio más usado: elegir por rendimiento ya no tiene sentido. Después de estos dos cambios, las dos actualizan solo lo que cambió. En cualquier aplicación real, la diferencia la van a hacer el tamaño de lo que descargás, cuántas consultas hace la pantalla y cómo se traen las imágenes — no cuál de las dos elegiste.

Lo que este cambio dice

Hay algo que va más allá de estas dos herramientas. Durante años, programar interfaces incluía una parte de trabajo dedicada a que la herramienta no hiciera de más: marcar qué no recalcular, evitar recorridos innecesarios.

Las dos, por vías opuestas, llegaron a que eso deje de ser responsabilidad de quien programa. Una lo resolvió pidiendo que declares las dependencias; la otra, deduciéndolas sola. En ambos casos, el resultado es que ese trabajo desaparece del código y queda espacio para lo que la aplicación efectivamente hace.

Comentarios