Todos los artículos

Reactividad y composablesAvanzado5 min de lectura

Reactividad de Vue 3.6: qué cambió con alien-signals y cómo medirlo

Qué cambió en el sistema de reactividad de Vue 3.6 con alien-signals, cuánto mejora medido en tu máquina y cómo comprobarlo en tu app.

Luis Aldair Quispe Rios

Objetivo y requisitos

Cuando hablamos de Vapor Mode, casi siempre hablamos del compilador. Pero debajo de Vapor hay otra pieza que cambió en Vue 3.6: el sistema de reactividad, que ahora usa una implementación derivada de alien-signals. Lo bueno es que no tienes que cambiar tu código para aprovecharlo.

En este artículo vemos qué cambió, cómo medirlo sin engañarte y qué esperar en una aplicación real. Necesitas Vue 3.6.0-rc.15 (la última RC al escribir esto) y Node.js ^22.18.0 o >=24.12.0, el rango que usa create-vue. Para las mediciones, usé Node v24.21.0 en Windows 11.

npm install vue@3.6.0-rc.15

Si tu proyecto usa Vue Router 5, recuerda el overrides de los posts anteriores: el router declara peer vue ^3.5.34 y npm no acepta una RC dentro de ese rango.

Qué cambió por dentro

Vue 3.5 tenía un sistema de reactividad de pull: cuando cambia un ref, marca como sucios a sus dependientes, y un computed se recalcula solo cuando alguien lo lee. Ese diseño es correcto, pero en escenas con muchos computed leídos después de un mismo cambio, la forma en que Vue recorría y marcaba dependencias era cara.

En diciembre de 2024, el PR #12349 portó a @vue/reactivity el núcleo de alien-signals (versión 0.4.4). Según la descripción del PR, el cambio busca tres cosas:

  • Menos memoria. El autor reporta una reducción de alrededor del 13 % al crear muchos ref, computed y effect (de 2,3 MB a 2,0 MB en su prueba).

  • Mejor rendimiento en lecturas masivas. El autor describe mejoras grandes en escenas donde muchos computed se leen después de cambiar un ref. Menciona más de 30 veces, proporcional a la escala, en un caso específico.

  • Código más simple. La planificación ya no depende de detalles externos como la limpieza de dependencias o los eventos de depuración.

La API no cambia: ref, computed, watch y effect se escriben igual. Lo que cambia es cómo el sistema decide qué recalcular.

Este mismo paquete es la base de Vapor. Si abres runtime-vapor en 3.6.0-rc.15, verás que importa ReactiveEffect, Dep, startBatch y computed desde @vue/reactivity. Por eso el rendimiento de Vapor depende en parte de esta reactividad.

Mide la misma escena en las dos versiones

Las cifras del PR son del autor, en su máquina y su escena. Para saber qué pasa en la tuya, mide la misma escena en las dos versiones. Instala ambas versiones de @vue/reactivity con alias:

npm install r35@npm:@vue/reactivity@3.5.45 r36@npm:@vue/reactivity@3.6.0-rc.15

Luego escribe una escena que cambie un ref y lea muchos computed:

import * as v35 from 'r35'
import * as v36 from 'r36'

function scene(api, { computeds, refs, readAll, withEffect }) {
  const { ref, computed, effect } = api
  const sources = Array.from({ length: refs }, (_, i) => ref(i))
  const comps = Array.from({ length: computeds }, (_, i) =>
    computed(() => sources[i % refs].value * 2),
  )
  let sink = 0
  if (withEffect) effect(() => { for (const c of comps) sink += c.value })
  return () => {
    sources[0].value++
    if (readAll) for (const c of comps) sink += c.value
  }
}

Cada operación se repite muchas veces, con calentamiento previo y la mediana de cinco rondas. Compara la misma escena con v35 y v36.

Los resultados en mi máquina

Estos son los tiempos por operación (en microsegundos, mediana de cinco rondas):

Escena

3.5.45

3.6.0-rc.15

Mejora

1.000 computeds: escribir y leer todos (sin efecto)

12,47 µs

7,53 µs

1,65×

1.000 computeds: escribir y leer todos (con efecto)

32,86 µs

18,52 µs

1,77×

1.000 computeds: escribir sin leer (con efecto)

19,28 µs

14,53 µs

1,33×

1 computed: escribir y leer (sin efecto)

0,05 µs

0,04 µs

1,08×

Tres lecturas:

  1. La mejora es real, pero es de 1,3× a 1,8×. Está lejos del 30× del PR. Ese número corresponde a una escena concreta a gran escala, y es distinto de lo que vas a notar en una aplicación.

  2. Donde más se nota es en lecturas masivas. Cuando lees muchos computed después de cada cambio, la mejora llega a 1,8×.

  3. Con un solo computed no hay diferencia medible. El valor es 0,04 a 0,05 µs, cerca de la resolución de performance.now(). No lo tomes como cifra.

Para la memoria no medí nada: el 13 % es del PR.

Lo que esto no significa

Estas son microbenchmarks de reactividad. No miden el render, el layout ni el paint. Una mejora de 1,8× en la reactividad se traduce en menos trabajo en el JavaScript, pero el tiempo que ve el usuario depende del resto de la pantalla. Lo vimos en el benchmark de Vapor: el layout del navegador se mantiene igual con los dos motores.

Tampoco hay que cambiar nada en tu código para obtener esta mejora. Eso es lo más valioso: si actualizas a 3.6 cuando salga la versión estable, tus computed y watch se benefician sin tocarlos. Pero también significa que no puedes medirla por cambios de código, sino por tus propias escenas.

Cómo medirlo en tu aplicación

Si quieres saber si la reactividad es el cuello de botella de una pantalla, no empieces por el benchmark. Primero mide la pantalla completa con la metodología del post de benchmark: build de producción, varias corridas, mediana. Si el tiempo está en el JavaScript de tus computed y no en el render, la reactividad puede importar. Si está en el render o en el layout, no.

Comprueba el resultado

  1. Instala r35 y r36 como arriba.

  2. Ejecuta la escena con tus números de computeds y refs, aproximándolos a los de tu pantalla.

  3. Repite la medición en build de producción, no en desarrollo.

  4. Compara la mediana de varias rondas, no una sola corrida.

Errores frecuentes

  • Citar el 30× como si fuera tu mejora. Es el caso del autor. Mide tu escena.

  • Medir un solo computed. La diferencia queda por debajo de la resolución del reloj.

  • Medir en desarrollo. Las comprobaciones de Vue distorsionan los tiempos.

  • Atribuir a la reactividad cambios de render. Si la pantalla mejora, separa el JavaScript de la reactividad y del render antes de concluir.

Cuándo importa

  • Pantallas con muchos computed derivados de pocos ref. Tablas, dashboards, filtros.

  • Listas con cambios frecuentes, donde cada cambio lee muchos valores derivados.

  • Antes de migrar a Vapor. La reactividad mejora ambos mundos; si ya tienes una pantalla con mucha reactividad, el beneficio se suma.

Si quieres ver cómo Vapor usa esta reactividad para actualizar solo el nodo afectado, el post sobre el compilador muestra la salida real.

Sigue leyendo