wandres.dev
GRANO FINO Y GRANO GRUESO · La distinción fundamental

No hay ganador: hay restricciones distintas

Qué optimiza cada modelo más allá del rendimiento — interrumpibilidad, robustez ante fallos, tamaño de runtime, superficie de bugs — y por qué las dos familias están convergiendo por los extremos.

⏱ 16 min

Después de dos lecciones de mecánica y una de costes, queda la parte que las comparativas nunca miden: qué optimiza cada modelo que no sea velocidad. Porque los equipos que diseñaron ambos no eran ingenuos, y las decisiones que tomaron responden a restricciones que se ven muy bien desde dentro de un producto grande y muy mal desde una demostración de listas.

🎯 Al terminar esta lección sabrás
  • Enumerar las propiedades no relacionadas con el rendimiento que optimiza cada modelo.
  • Entender por qué la interrumpibilidad exige un árbol descriptivo.
  • Comparar la superficie de bugs característica de cada familia.
  • Reconocer los movimientos de convergencia entre ambas.

Lo que optimiza la reconciliación

Robustez ante fallos. Cada ciclo se calcula desde el estado actual sin depender del anterior. Si un ciclo falla a mitad, el siguiente parte de cero y el sistema se recupera solo. En un motor incremental, en cambio, un fallo a mitad de propagación deja el grafo en un estado que no corresponde a ninguna entrada válida: parte de los nodos actualizados y parte no, sin nada que lo corrija. Esta propiedad importa mucho en aplicaciones de larga vida que no se recargan durante horas.

Interrumpibilidad. Como el trabajo produce una descripción antes de tocar nada, se puede pausar, ceder el hilo, y descartar sin consecuencias. Toda la maquinaria de renderizado concurrente se apoya en eso. Un motor de grano fino que escribe en el DOM mientras propaga no puede descartar trabajo ya hecho: para poder hacerlo tendría que introducir un búfer intermedio, es decir, reinventar el árbol descriptivo.

Ausencia de una clase entera de bugs. Sin suscripciones no hay fugas de suscripción, ni aristas obsoletas, ni efectos que sobreviven a lo que los creó, ni el problema de la limpieza en cascada. Comparado con el nivel 7 de este track, que existe entero para resolver ese problema, es una simplificación considerable.

Modelo mental uniforme. Todo es una función del estado a la salida. No hay dos clases de valor —el que rastrea y el que no—, ni la distinción entre leer y suscribirse, ni la trampa de la desestructuración. Cuesta menos enseñarlo, y en un equipo grande eso es una restricción real.

Lo que optimiza el grano fino

Coste de actualización independiente del tamaño. Ya lo hemos cuantificado. Es la propiedad principal y es sólida.

Runtime pequeño. No hay reconciliador, no hay planificador de prioridades, no hay árbol descriptivo. Un motor de señales completo cabe en unos pocos kilobytes, frente a las decenas de un reconciliador maduro. Para widgets embebidos y para aplicaciones que compiten por bytes, es decisivo.

Predictibilidad de la ejecución. El código del componente corre una vez. No hay que razonar sobre qué se recrea entre renders, ni sobre identidades de función, ni sobre listas de dependencias que hay que mantener sincronizadas a mano. Una clase entera de errores —olvidar una dependencia en un efecto— desaparece porque el motor las descubre él.

El grafo es inspeccionable. Como las dependencias son datos en memoria, se pueden recorrer, dibujar y depurar. Herramientas de desarrollo pueden mostrarte literalmente qué depende de qué. En reconciliación no hay grafo que enseñar, porque las dependencias nunca se materializan.

flowchart TB
R[reconciliacion] --> R1[robustez ante fallos]
R --> R2[interrumpible]
R --> R3[sin fugas de suscripcion]
G[grano fino] --> G1[coste independiente del tamano]
G --> G2[runtime pequeno]
G --> G3[grafo inspeccionable]
style R fill:#89b4fa,color:#11111b
style G fill:#cba6f7,color:#11111b
style R1 fill:#a6e3a1,color:#11111b
style R2 fill:#a6e3a1,color:#11111b
style R3 fill:#a6e3a1,color:#11111b
style G1 fill:#a6e3a1,color:#11111b
style G2 fill:#a6e3a1,color:#11111b
style G3 fill:#a6e3a1,color:#11111b

Las superficies de bugs son complementarias

Es útil comparar no las virtudes sino los modos de fallo típicos, porque es con ellos con lo que vas a convivir.

En reconciliación, los bugs característicos son de exceso de trabajo: algo se recrea cuando no debía porque una identidad cambió entre renders, un efecto se dispara de más porque una dependencia no era estable, una lista se reconstruye por una clave mal elegida. Son bugs de rendimiento, molestos pero visibles y medibles.

En grano fino, los bugs característicos son de defecto de trabajo: algo no se actualiza porque la lectura ocurrió fuera del ámbito rastreado, porque se desestructuró un objeto reactivo, o porque la lectura estaba tras un await. Son bugs de corrección, y son silenciosos: no hay error, simplemente una parte de la pantalla se queda congelada.

⚠️
Ruidoso frente a silencioso

Un bug de exceso te avisa: el perfilador lo enseña, el usuario nota lentitud. Un bug de defecto no avisa: la interfaz miente y nadie lo detecta hasta que alguien mira dos veces. En términos de coste de mantenimiento eso no es un empate. Es la razón de fondo por la que los motores de grano fino invierten tanto en advertencias de desarrollo y en reglas de lint que detectan lecturas fuera de ámbito.

Hacia dónde van las dos familias

La convergencia

Lo más interesante de 2026 es que las dos familias están adoptando las ideas de la otra por los extremos.

La familia de reconciliación ha automatizado la memoización. React Compiler alcanzó la versión 1.0 estable en octubre de 2025, e inserta en compilación las memoizaciones que antes había que escribir a mano. No cambia de familia —sigue reejecutando y comparando— pero reduce S acercándolo a K, que es exactamente la dirección del grano fino.

La familia de grano fino ha adoptado la ergonomía de componentes. Vue 3.6, en fase de candidata a versión final desde julio de 2026, trae Vapor Mode: compilación sin árbol descriptivo intermedio, opcional y conviviendo con el modo clásico en la misma aplicación. Y ha reescrito por completo su paquete de reactividad sobre alien-signals, una implementación de señales con menos sobrecarga y menor consumo de memoria, que beneficia a todo el mundo use o no Vapor.

Angular llegó a la convergencia desde el otro lado: sustituyó la detección de cambios basada en parcheo global de la asincronía por un grafo de señales. La detección de cambios sin zonas es estable desde la versión 20.2, y desde la 21 las aplicaciones nuevas ya no incluyen zone.js por defecto.

La pregunta correcta no es cual, sino cuanto te cuesta expresar un grafo fino

Después de todo el nivel, la conclusión que de verdad sirve para decidir es esta: dado que la granularidad efectiva la escribe el programador y no el framework, y dado que ambas familias convergen hacia el mismo punto por caminos opuestos, la variable que queda es cuánto esfuerzo te cuesta describir un grafo bien granulado en cada herramienta. En un motor de grano fino ese esfuerzo tiende a cero porque la granularidad es el comportamiento por defecto: cada expresión ya es un nodo, y para empeorarla hay que esforzarse metiéndolo todo en un memo gigante. En uno de reconciliación el esfuerzo era históricamente alto —memoizar a mano, mantener listas de dependencias, estabilizar identidades— y es justo eso lo que un compilador automatiza, con lo que la distancia se estrecha mucho. Elegir por rendimiento bruto es casi siempre elegir mal, porque los tres regímenes de la lección anterior te dirán que da igual en tu caso. Elegir por el coste de expresar bien tu grafo, por el modo de fallo con el que prefieres convivir, y por lo que tu equipo ya sabe, es elegir por las variables que de verdad dominan el resultado del proyecto. Quien ha construido un motor entero —lo harás en el nivel 12— deja de tener bando, porque ve que las dos familias son la misma máquina con la aguja del grano en distinta posición.

Lo que sigue

A partir de aquí el track abandona la comparación y se dedica a la mecánica interna de la familia de grano fino, que es la que tiene una estructura de datos que estudiar. El nivel 2 abre el grafo y mira sus nodos y sus aristas.

⚔️ Escribe la lista de restricciones de tu proyecto
  1. Enumera las restricciones reales de un proyecto tuyo: tamaño del bundle, tiempo hasta interactivo, actualizaciones por segundo, tamaño y experiencia del equipo.
  2. Para cada una, indica cuál de las dos familias la favorece y por qué, citando un mecanismo concreto.
  3. Cuenta cuántas restricciones apuntan a cada lado y comprueba si el resultado coincide con la decisión que se tomó en su momento.
  4. Identifica la restricción que de verdad decidió, y comprueba si era técnica.