Vue: proxies, ámbitos y planificador asíncrono
Las decisiones de Vue: reactividad profunda por proxy como opción de primera clase, ámbitos de efecto para el ciclo de vida, planificación asíncrona con tres momentos de vaciado, y la reescritura sobre alien-signals.
Vue llegó a las señales antes de que se llamaran así y ha ido convergiendo con el resto de la familia sin abandonar sus decisiones distintivas: el proxy como mecanismo central, un planificador asíncrono con momentos de vaciado explícitos, y ámbitos de efecto en lugar de un árbol de dueños con nombre propio. La reescritura de su capa reactiva en la versión 3.6 la ha acercado todavía más al resto en implementación, sin cambiar su superficie.
- Situar las decisiones de Vue en las ocho dimensiones del track.
- Distinguir
refdereactivepor lo que hace cada uno en el grafo. - Entender los tres momentos de vaciado de un watcher.
- Reconocer qué cambió con la reescritura sobre alien-signals.
Las ocho decisiones
Representación del grafo. Tras la reescritura de la versión 3.6, listas doblemente enlazadas con reutilización de aristas, siguiendo el diseño de alien-signals. Antes usaba conjuntos con recuento de versiones. El cambio no altera la semántica, solo las constantes: menos asignaciones y menos memoria.
Descubrimiento. Tracking automático con una variable de suscriptor activo. La lectura pasa por la trampa get de un proxy o por el getter de una ref.
Propagación. Push-pull con marcado y verificación perezosa por niveles de suciedad, con comprobación por versión para decidir si un valor cambió de verdad.
Consistencia. Glitch-free, reforzada por el planificador asíncrono: al agruparse todo el turno, los efectos ven un estado ya estabilizado.
Memoización. computed, perezoso y cacheado. La opción de igualdad se consigue con customRef cuando hace falta control total.
Ciclo de vida. effectScope, con getCurrentScope y onScopeDispose. Es el árbol de dueños del nivel 7 con otro nombre, y admite crear ámbitos desacoplados.
Planificación. Asíncrona por defecto, con nextTick para esperar el vaciado. Cada watcher elige su momento con flush, que admite pre, post y sync.
Modelo de estado. Los dos, con nombres distintos: ref para lo atómico y reactive para lo profundo, más shallowRef y shallowReactive para los casos intermedios y markRaw para excluir.
ref frente a reactive
La distinción es la del nivel 9 y conviene verla en términos del grafo.
Una ref es una fuente con un valor. El acceso es por la propiedad .value, que es un getter y un setter normales.
const contador = ref(0);
contador.value++; // una fuente, una escritura
Un reactive es un proxy que crea una fuente por propiedad de forma perezosa.
const estado = reactive({ a: 1, b: { c: 2 } });
estado.b.c = 3; // fuente de la propiedad c del objeto anidado
La documentación de Vue recomienda ref como opción por defecto, y la razón principal es la trampa de la desestructuración: reactive la sufre y ref no, porque .value obliga a que el acceso ocurra donde se usa. Para los casos donde se quiere desestructurar un objeto reactivo, toRefs convierte cada propiedad en una ref independiente.
Dentro de una plantilla, Vue desenvuelve las ref de nivel superior automáticamente, así que se escribe contador y no contador.value. Es azúcar del compilador y es cómodo, y a la vez es una fuente de confusión al mover código entre la plantilla y el script, donde .value sí hace falta. Es un buen ejemplo de la tensión del nivel 9 entre ergonomía y visibilidad.
Los tres momentos de vaciado
Vue es el motor que expone la planificación con más granularidad, y es una decisión coherente con ser parte de un framework con ciclo de renderizado propio.
flush: 'pre', el valor por defecto. El watcher corre antes de que el componente se vuelva a renderizar. Es el momento para actualizar estado derivado que la plantilla va a leer.
flush: 'post'. Corre después de que el DOM se haya actualizado. Es el momento para medir geometría, mover el foco o inicializar librerías que necesitan elementos ya montados.
flush: 'sync'. Corre de forma síncrona con la escritura, sin encolar. Es el modo del que la documentación advierte, porque con varias escrituras produce ejecuciones repetidas y pierde el agrupamiento.
watch(fuente, hacer); // pre, antes del render
watch(fuente, medir, { flush: 'post' }); // post, con el DOM ya actualizado
watchEffect(reaccionar, { flush: 'sync' }); // sincrono, sin agrupar
Que existan tres y no dos reconoce algo importante que el nivel 8 anticipaba: el pintado del navegador es un evento con el que hay que poder coordinarse, y un motor que solo ofrece antes y después de la propagación se queda corto.
flowchart TB W[escritura] --> C[cola de jobs] C --> P1[watchers pre] P1 --> R[renderizado del componente] R --> P2[watchers post] P2 --> F[turno terminado] W -.flush sync.-> S[ejecucion inmediata] style W fill:#89b4fa,color:#11111b style C fill:#f9e2af,color:#11111b style P1 fill:#cba6f7,color:#11111b style R fill:#fab387,color:#11111b style P2 fill:#a6e3a1,color:#11111b style F fill:#a6e3a1,color:#11111b style S fill:#f38ba8,color:#11111b
La reescritura sobre alien-signals
En la versión 3.6, en fase de candidata a versión final desde julio de 2026, Vue reescribió por completo su paquete de reactividad sobre alien-signals, una implementación de señales con menor sobrecarga y menor consumo de memoria. La ganancia es de constantes: menos asignaciones por reejecución gracias a la reutilización de aristas, y menos memoria por nodo.
Es un buen ejemplo de la conclusión del nivel 2: el algoritmo estaba resuelto y la ganancia estaba en el diseño de memoria. La semántica no cambia, el código de usuario no cambia, y el rendimiento mejora de forma medible en árboles grandes.
La misma versión trae Vapor Mode, un modo de compilación sin árbol descriptivo intermedio, con la reactividad conectando directamente con el DOM al estilo del nivel 1. Es completamente opcional y convive con el modo clásico en la misma aplicación, lo que es una decisión de migración interesante: el componente decide, no la aplicación.
Si te preguntas por qué Vue tiene dos modelos de estado, tres momentos de vaciado, dos modos de compilación y una API de opciones junto a otra de composición, la respuesta no es indecisión: es que Vue optimiza por encima de casi todo para que el código existente siga funcionando. Cada decisión que parece redundante es un camino de migración para alguien. reactive existe porque la versión 2 tenía esa semántica y millones de líneas dependen de ella; ref existe porque es el modelo que escala mejor y hacia el que se recomienda ir. Vapor Mode es opcional por componente porque migrar una aplicación entera de golpe es inviable. Esta restricción —evolucionar sin romper— es de una naturaleza distinta a las que optimizan los demás motores, y hay que reconocerla para juzgar el diseño con justicia: un motor que puede empezar de cero siempre tendrá una superficie más limpia que uno que arrastra una década de código en producción. Que Vue haya conseguido reescribir su núcleo reactivo por completo, adoptar el estado del arte en representación de aristas y añadir un modo de compilación sin árbol intermedio, todo ello sin romper el código de sus usuarios, es un logro de ingeniería que las comparativas de rendimiento no miden y que en un proyecto real vale más que cualquier diferencia de milisegundos.
- Escribe tres watchers sobre la misma fuente, uno de cada tipo, y registra el orden de ejecución.
- En el de tipo post, mide la geometría de un elemento y comprueba que refleja el cambio.
- En el de tipo pre, mide lo mismo y comprueba que refleja el estado anterior.
- Escribe cinco veces la fuente seguidas y cuenta las ejecuciones de cada uno de los tres.