La convergencia de frameworks
Los frameworks de interfaz partieron de premisas incompatibles y están aterrizando todos en el mismo par de decisiones: un grafo reactivo de grano fino como motor de actualización y un compilador que traslada trabajo del tiempo de ejecución al tiempo de construcción. Esta lección explica por qué esa convergencia era inevitable —dos presiones selectivas, la del coste por actualización y la del coste cognitivo de la memoización manual—, recorre el camino distinto por el que llegó cada familia, y aborda la pregunta que de verdad importa: si el motor acaba siendo el mismo para todos, qué queda como diferencia real. La respuesta no está en la velocidad ni en la sintaxis, sino en el modelo de composición, en la historia de servidor de cada uno, en la disciplina de mutación que imponen o no, y en las propiedades del ecosistema que nunca aparecen en un banco de pruebas.
Nada une tanto como una restricción compartida. Hace diez años los frameworks discutían premisas de fondo: si la interfaz debía describirse y compararse o cablearse una vez, si la reactividad debía ser explícita o mágica, si el trabajo debía ocurrir en el navegador o en el proceso de construcción. Eran desacuerdos honestos y cada bando tenía razones. Hoy, si abres el código generado por React con su compilador, por Svelte 5, por Vue en su modo compilado, por Angular con signals o por Solid, ves cinco caminos distintos llegando al mismo destino: un grafo de dependencias de grano fino cuyo cableado se decide en tiempo de compilación. Cuando cinco linajes que no se copian aterrizan en la misma forma, no estás mirando una moda: estás mirando una presión selectiva. Y la pregunta interesante deja de ser cuál gana, para pasar a ser qué diferencia queda cuando el motor es el mismo.
- Identificar las dos presiones que empujan a todo framework hacia el grano fino y hacia el compilador.
- Reconstruir el camino por el que llegó cada familia y qué renunció por el camino.
- Enumerar con precisión qué diferencias reales quedan cuando el motor de actualización converge.
- Reformular el criterio de elección de framework en términos que sobrevivan a la convergencia.
Dos gravedades: el grafo fino y el compilador
La primera presión es el coste por actualización. Un modelo que invalida componentes enteros hace un trabajo proporcional al tamaño de la región reejecutada, aunque el cambio afecte a un solo carácter. Un grafo que conoce las dependencias hace un trabajo proporcional al número de consumidores reales del dato. Mientras las aplicaciones fueron pequeñas la diferencia era teórica; en cuanto dejaron de serlo, el grano fino pasó de ventaja a requisito.
La aritmética es la que decide, y es despiadada. Con diez componentes por rama y actualizaciones esporádicas, el trabajo de más es invisible y ningún usuario lo nota. Con listas de miles de filas, paneles que se refrescan varias veces por segundo y árboles de veinte niveles, ese mismo trabajo de más se convierte en fotogramas perdidos, y el modelo grueso empieza a necesitar barreras manuales solo para mantenerse a flote.
La segunda presión es más sutil y ha resultado más decisiva: el coste cognitivo de la memoización manual. Un modelo grueso puede alcanzar el rendimiento de uno fino si el programador coloca a mano las barreras que podan el cono de invalidación. El problema es que colocarlas bien exige razonar sobre identidades de referencia en cada frontera, es fácil de hacer mal, y ese razonamiento no aporta nada al producto. Cuando una tarea es mecánica, verificable y odiada, acaba en un compilador. Siempre.
Vale la pena notar que esa segunda presión no es de rendimiento sino de ergonomía, y por eso es la que ha movido a los proyectos más grandes. Un equipo tolera durante años un modelo algo más lento; lo que no tolera es que cada persona nueva tarde meses en aprender a colocar barreras de memoización y siga colocándolas mal después. El coste de enseñanza es el coste que más pesa en una organización, y es invisible en cualquier medición técnica.
Las dos presiones apuntan al mismo sitio porque son la misma pregunta vista desde dos alturas: quién depende de qué. El grafo fino responde en tiempo de ejecución registrando las lecturas; el compilador responde en tiempo de construcción analizando el código. La convergencia consiste en hacer ambas cosas a la vez, y usar cada una donde es más barata.
El reparto entre ambas no es arbitrario. Lo que el análisis estático puede demostrar sin ejecutar nada —que este fragmento de plantilla nunca cambia, que esta expresión solo depende de estos dos identificadores, que este componente no lee ningún estado externo— se resuelve en construcción y desaparece del paquete que se envía. Lo que depende de datos que solo existen en ejecución —qué rama se tomó, qué elementos hay en esta lista, qué señal se leyó dentro de esta condición— se resuelve con el grafo. Cada mecanismo se queda con la mitad de la pregunta que puede responder gratis.
Hay una tercera presión, menos técnica pero igual de real, que acelera el proceso: la comparabilidad. Cuando existen bancos de pruebas públicos y todos miden lo mismo, una decisión de diseño que produzca peores números se vuelve indefendible aunque tenga otras virtudes. Los bancos de pruebas no descubren la verdad, pero sí estrechan el rango de respuestas socialmente aceptables, y eso empuja a los rezagados hacia donde ya están los demás.
flowchart TD P1[presion: coste por actualizacion] --> G[grafo de grano fino] P2[presion: memoizacion manual] --> C[compilador] G --> X[motor comun de 2026] C --> X X --> D1[queda: modelo de composicion] X --> D2[queda: historia de servidor] X --> D3[queda: disciplina de mutacion] X --> D4[queda: ecosistema y equipo] style X fill:#cba6f7,color:#11111b
Cinco caminos, un destino
Antes de recorrerlos, conviene fijar el punto de partida de cada linaje, porque la distancia recorrida explica lo que a cada uno le costó. Uno venía del grafo sin compilador, otro del compilador sin grafo, otro de la comparación de árboles y otro de la detección de cambios por recorrido. Ninguno eligió su destino: lo encontró empujado por las dos presiones anteriores.
Solid fue el que partió más cerca: nació con grano fino y con un compilador que convierte la sintaxis de plantilla en operaciones directas sobre el DOM, sin representación intermedia que comparar. No tuvo que converger; el resto convergió hacia donde ya estaba.
Su papel histórico ha sido el de prueba de existencia, y no conviene subestimarlo. Demostró que se podía conservar una sintaxis familiar y una ergonomía cómoda sin pagar el coste de reejecutar componentes, y eso desactivó el argumento de que el grano fino obligaba a renunciar a la comodidad. A partir de ahí, quedarse en el modelo grueso dejó de ser una elección de diseño y pasó a ser deuda.
Svelte llegó desde el compilador y tardó en llegar al grafo. Durante años su reactividad era una reescritura de asignaciones en tiempo de compilación, elegante pero atada al ámbito del componente y con fronteras incómodas. Su quinta versión sustituyó ese truco por señales explícitas, y con ello aceptó un pequeño coste de ceremonia a cambio de una reactividad que atraviesa módulos.
Ese cambio ilustra bien la diferencia entre las dos capas. Por fuera, la quinta versión se parece muchísimo a la cuarta y el código de aplicación apenas cambia de aspecto. Por dentro, el modelo de propagación es otro, y con él se arreglaron de golpe media docena de limitaciones que la comunidad llevaba años esquivando con patrones. La convergencia se nota poco en la superficie y muchísimo en qué deja de ser imposible.
Vue tenía el grafo desde el principio, y lo que fue añadiendo es compilación: primero para las plantillas, después para saltarse la comparación de los nodos que el análisis demuestra estáticos, y finalmente para prescindir de la representación intermedia allí donde puede.
El resultado tiene una consecuencia curiosa: dentro del mismo framework conviven dos motores, uno para lo que el análisis puede demostrar estático y otro para lo dinámico. Eso complica su implementación pero simplifica la vida de quien lo usa, porque el reparto lo decide el compilador y no aparece en el código de aplicación.
Angular hizo el viaje más largo, porque venía de un modelo de detección de cambios que recorría el árbol entero disparado por parches globales sobre las APIs del navegador. Cambiarlo por signals y por un modo sin ese mecanismo de intercepción no fue una optimización: fue reconocer que el modelo de propagación anterior era una respuesta de otra época.
Merece la pena detenerse en lo que renunció cada uno, porque las renuncias explican mejor la convergencia que las adopciones. Svelte renunció a la magia de la asignación, que era su rasgo más querido y su mejor argumento de venta, al descubrir que no escalaba fuera del componente. Angular renunció a un mecanismo de detección que definía su identidad y que obligaba a parchear las APIs del navegador. Vue renunció a la representación intermedia allí donde el compilador puede prescindir de ella, aceptando dos caminos internos en lugar de uno.
React es el caso más interesante porque es el que más tiene que perder. Su modelo de componente reejecutado es el origen de su simplicidad conceptual, y renunciar a él sería renunciar a su identidad. Su apuesta ha sido conservar el modelo y quitarle el peaje: un compilador que inserta la memoización que el programador escribía a mano, y una participación activa en la estandarización del grafo para tenerlo disponible por debajo. Mantiene la unidad de organización y adopta el mecanismo de propagación.
Ese movimiento tiene una lectura que va más allá de un framework concreto. Durante años el debate se planteó como una disyuntiva —o componentes reejecutados o grafo fino— y resultó ser un falso dilema. El componente es una buena unidad de organización, de carga diferida y de frontera con el servidor; el grafo es un buen mecanismo de propagación. No compiten por el mismo puesto, y la convergencia consiste en dejar a cada uno con el trabajo que hacía bien.
Un ejercicio que aclara más que cualquier comparativa: coge la misma lista con un filtro y escríbela en dos frameworks distintos, después mira lo que produce el compilador de cada uno. Vas a encontrar las mismas piezas con otros nombres —creación única de nodos, expresiones reactivas atadas a un punto concreto del DOM, un recorrido por claves para reordenar—. La sintaxis de entrada se parece cada vez menos entre frameworks y la salida se parece cada vez más, y eso es exactamente lo que significa que la convergencia ocurra en la capa de mecanismo y no en la de superficie.
Que cinco sistemas adopten la misma pareja de decisiones no los vuelve intercambiables, igual que dos lenguajes con recolección de basura no son el mismo lenguaje. La convergencia ocurre en la capa de mecanismo, que es donde había una única respuesta correcta y donde las diferencias eran, en el fondo, deuda histórica. Por encima de esa capa sigue habiendo desacuerdos legítimos que no tienen respuesta única y que por tanto no van a converger. Confundir las dos capas produce las dos conclusiones erróneas de esta década: creer que ya da igual cuál elijas, y creer que sigue siendo la decisión más importante del proyecto.
Lo que queda como diferencia real
Modelo de composición
Qué es una unidad de interfaz, cómo se pasan datos hacia abajo y eventos hacia arriba, cómo se comparte lógica. Es la parte que tocas cada día y la que menos converge.
Historia de servidor
Componentes de servidor, islas o reanudación. Es el desacuerdo vivo más importante y el que decide qué código llega al navegador y cuándo.
Disciplina de mutación
El grafo dice cómo viaja un cambio, nunca quién puede provocarlo. Ese segundo eje sigue vacío en todos los frameworks y sigue siendo trabajo tuyo.
Propiedades del ecosistema
Estabilidad entre versiones mayores, calidad de las herramientas, tamaño del mercado laboral y política de compatibilidad. Nada de esto aparece en un banco de pruebas y todo decide proyectos.
Antes de mirarlas una a una, conviene ordenar la lista por su peso real en un proyecto, que no coincide con el orden en que aparecen en las comparativas. La historia de servidor y las propiedades del ecosistema deciden más que ninguna otra cosa; el modelo de composición decide tu día a día; la disciplina de mutación decide cuántos bugs difíciles vas a tener. La velocidad, que es lo que se discute en público, ya casi no decide nada.
La primera es la que subestima todo el mundo cuando compara frameworks, porque no aparece en ningún banco de pruebas y sin embargo es el ochenta por ciento del tiempo que pasas escribiendo. Cómo se declara una unidad de interfaz, si la lógica reutilizable se empaqueta como función, como directiva o como clase, cómo se comunica un hijo con su padre, qué ocurre con los estilos, cómo se tipa el contrato entre dos piezas. Esas decisiones no tienen respuesta óptima y por eso van a seguir divergiendo indefinidamente.
De las cuatro, la que más peso tendrá en los próximos años es la historia de servidor, porque es la única donde los diseños siguen siendo genuinamente incompatibles. Un modelo que ejecuta componentes en el servidor y envía su resultado como parte del árbol, otro que envía islas independientes que se hidratan por separado, y otro que serializa el estado de la ejecución para reanudarla en el cliente sin repetirla son tres respuestas distintas a la misma pregunta, y ninguna es obviamente superior.
Que no converjan tiene una explicación limpia: no responden a un problema técnico con óptimo, sino a un compromiso entre variables que cada producto pondera distinto. Cuánto código llega al navegador, cuánto tarda la primera interacción, cuánta lógica de datos puede quedarse en el servidor, cuánta complejidad de despliegue estás dispuesto a asumir. Cambia el peso de esas variables y cambia la respuesta correcta, así que las tres van a seguir vivas.
Y esa frontera arrastra al estado de una forma que la convergencia del motor no toca. Decidir qué componentes corren en el servidor es decidir qué estado no puede existir en ellos, y por tanto reorganiza la clasificación entera que has aprendido: qué es local, qué es compartido, qué es cache de servidor y qué vive en la dirección del navegador. Dos frameworks con el mismo motor reactivo pueden exigirte arquitecturas de estado completamente distintas por este único motivo.
La segunda en peso es la disciplina de mutación, y merece un aviso porque la convergencia la deja más expuesta que nunca. Cuando todos los frameworks te dan propagación excelente, la tentación es concluir que el problema del estado está resuelto. No lo está: solo está resuelto uno de los dos ejes. Un grafo perfecto sobre un estado que cualquiera escribe desde cualquier sitio propaga con exquisita precisión un desastre.
De hecho la convergencia empeora ligeramente el riesgo, porque la facilidad de escribir en una señal desde cualquier módulo elimina la fricción que antes desalentaba el estado global desordenado. Cuando compartir estado costaba montar un proveedor y cablear un contexto, esa fricción hacía de barrera involuntaria. Con un primitivo que se exporta desde cualquier fichero, la barrera desaparece y solo queda la disciplina que tú decidas imponerte.
La cuarta diferencia parece la menos técnica y es la que más proyectos ha decidido. La estabilidad entre versiones mayores determina cuántas semanas al año dedicas a migrar en vez de construir; la calidad del análisis de tipos determina cuántos errores llegan a producción; el tamaño del mercado laboral determina si puedes crecer el equipo. Nada de eso se mide en milisegundos y todo eso se paga durante años.
// mismo motor, dos arquitecturas distintas por encima
const carrito = signal<Item[]>([])
carrito.set([...carrito(), item]) // libre: cualquiera escribe
const carrito2 = crearStore(reducer, []) // gobernado: solo hechos con nombre
carrito2.enviar({ tipo: 'anadir', item }) // el motor de abajo es el mismo
Elegir cuando el motor ya no distingue
Si el mecanismo converge, el criterio de elección tiene que mudarse a las diferencias que quedan, y eso cambia las preguntas que hay que hacer. Deja de preguntar cuál es más rápido, porque la respuesta va a ser la misma para todos en cuanto el compilador y el grafo estén maduros, y va a estar dominada por decisiones tuyas de granularidad y de dónde vive el estado.
Ese último punto conviene subrayarlo porque desmonta una excusa muy repetida. En una aplicación con el motor convergido, la diferencia de rendimiento entre dos frameworks es mucho menor que la diferencia entre dos formas de organizar el mismo estado dentro de uno solo de ellos. Un estado bien partido en el framework menos veloz supera con holgura a un estado monolítico en el más veloz, así que atribuir la lentitud a la herramienta suele ser un diagnóstico que evita mirar donde duele.
Pregunta, en cambio, qué modelo de servidor encaja con tu producto, porque una aplicación con mucho contenido público y una herramienta interna con sesión permanente tienen necesidades opuestas. Pregunta qué compromiso de compatibilidad ofrece el proyecto, porque el coste de un framework se paga en migraciones y no en el primer mes. Pregunta qué disciplina de mutación vas a poner encima, porque el framework no la trae y es el eje donde nacen los bugs caros. Y pregunta a quién vas a contratar, que es un argumento de ingeniería y no una concesión.
Añade una pregunta más que casi nadie hace: qué parte de tu código quedaría intacta si dentro de cinco años cambiaras de framework. Si la respuesta es poca, el problema no es la elección, es que la lógica de tu producto está disuelta dentro de la vista, y esa deuda la vas a pagar con cualquier herramienta que elijas.
Hay una consecuencia agradable de todo esto para la carrera de quien está leyendo. Si el motor converge, cambiar de framework deja de ser una reeducación y pasa a ser un cambio de dialecto: los conceptos que sostienen tu trabajo diario —qué es una dependencia, cuándo se recalcula algo, por qué esta parte no se actualiza— son los mismos en todos, y solo cambia cómo se escriben. La ansiedad de estar atado a una tecnología concreta se disuelve en la medida en que hayas aprendido el motor y no la sintaxis.
Queda una advertencia final sobre el calendario. La convergencia es una dirección, no un hecho consumado, y durante bastantes años convivirán bases de código escritas antes y después del giro. Elegir hoy implica convivir con esa mezcla, y la habilidad valiosa no es apostar por el ganador sino diseñar la capa de estado de forma que el giro, cuando llegue, solo obligue a reescribir la vista.
Hay una regularidad histórica que explica esta década entera y que conviene interiorizar porque va a volver a ocurrir. Toda tecnología joven compite en mecanismo, porque el mecanismo aún no está resuelto y la diferencia de rendimiento entre dos respuestas es enorme. Toda tecnología madura deja de competir en mecanismo, porque la mejor respuesta se descubre, se copia y se vuelve suelo común. Pasó con los recolectores de basura, que dejaron de ser un argumento de venta entre lenguajes. Pasó con los motores de JavaScript, que dejaron de ser una razón para elegir navegador. Está pasando ahora con la reactividad. Y cada vez que ocurre, la gente que había construido su identidad profesional sobre el mecanismo siente que su conocimiento se devalúa, mientras que la gente que había construido su criterio sobre la capa de arriba descubre que su conocimiento acaba de volverse más valioso, porque la capa de arriba es donde queda toda la decisión. La lección práctica es directa: mide cuánto de lo que sabes es mecanismo y cuánto es criterio. Si dominas el sistema de memoización de un framework concreto, sus reglas de dependencias y sus trucos de identidad referencial, estás en el lado que el compilador va a absorber, y lo va a hacer pronto. Si dominas cómo separar clases de estado, dónde colocar la frontera de servidor, qué disciplina de mutación merece cada parte del producto y cómo se paga una migración a cinco años vista, estás en el lado que ninguna convergencia toca. Los frameworks convergen en lo que tiene respuesta única; lo que no tiene respuesta única no converge nunca, y por eso es lo único que vale la pena aprender a decidir.
- Coge tres frameworks distintos y describe, en una frase cada uno, cómo propagan un cambio; comprueba cuánto se parecen ya esas tres frases.
- Toma una lista de lo que sabes de tu framework principal y clasifica cada elemento como mecanismo absorbible por un compilador o como criterio propio.
- Escribe la misma pantalla con dos disciplinas de mutación distintas sobre el mismo motor reactivo y compara qué clase de bug hace imposible cada una.
- Compara las tres historias de servidor —componentes de servidor, islas y reanudación— para un producto concreto tuyo y justifica cuál encaja.
- Estima el coste real de migrar tu proyecto entre dos frameworks y separa qué parte del coste es la vista y qué parte es el estado.
- Formula tres preguntas de elección de framework que sigan teniendo sentido cuando el motor de todos sea idéntico.