on: dependencias explícitas y defer
on invierte el rastreo automático: declaras a mano qué fuentes disparan la reacción y el resto del cuerpo corre sin suscribirse a nada. La forma on con una o varias deps, el callback que recibe valor actual, previo y retorno anterior, y la opción defer para saltar la ejecución inicial y reaccionar solo a cambios posteriores, como un watcher.
El rastreo automático es la gloria de Solid, pero a veces quieres lo contrario: control total sobre qué dispara una reacción, sin que una lectura incidental cuele una dependencia que no pediste. on es el ayudante que invierte el modelo. En lugar de descubrir las dependencias por el acto de leer, las declaras por adelantado en una lista explícita, y el resto del cuerpo corre a oscuras, sin suscribir a nada. Es el puente entre la reactividad implícita de Solid y la mentalidad de watcher explícito que traen quienes vienen de Vue o de MobX.
- Entender que
onhace explícitas las dependencias y desactiva el rastreo del cuerpo. - Usar
oncon una fuente y con un array de fuentes, y leer sus argumentos. - Aprovechar el callback que recibe valor actual, valor previo y retorno anterior.
- Aplicar
defer: truepara saltar el run inicial y reaccionar solo a cambios.
Declarar las dependencias en vez de descubrirlas
on no es una primitiva reactiva por sí misma: es una fábrica de cuerpos que entregas a createEffect, createMemo o createComputed. Recibe las dependencias y una función, y devuelve otra función que rastrea solo esas dependencias y ejecuta el cuerpo sin rastrear nada más.
import { createSignal, createEffect, on } from "solid-js";
const [a, setA] = createSignal(0);
const [b, setB] = createSignal(0);
createEffect(
on(a, (valorA) => {
console.log("reacciona a A:", valorA, "y lee B:", b()); // b NO es dependencia
})
);
Este efecto se dispara únicamente cuando cambia a. Dentro del cuerpo lees b() con total libertad y sin consecuencias: como el cuerpo corre fuera del rastreo, esa lectura no crea suscripción. Compáralo con el modelo por defecto, donde leer b() ahí dentro te ataría a b sin remedio. on te da un cuerpo hermético cuya única puerta de entrada es la lista de deps.
El callback recibe más de lo que parece. Su firma completa es (input, prevInput, prevResultado) => resultado: el valor actual de las deps, el valor que tenían en la ejecución anterior, y lo que el propio callback devolvió la última vez.
createEffect(
on(a, (actual, previo, cuenta) => {
console.log(`A pasó de ${previo} a ${actual}`);
return (cuenta ?? 0) + 1; // acumulador: cuántas veces reaccionó
})
);
Varias fuentes y su uso con memo
Cuando pasas un array de accesores, on rastrea todos, y el callback recibe arrays paralelos para el valor actual y el previo. Cualquiera de las fuentes que cambie dispara la reacción.
createEffect(
on([a, b], ([na, nb], [pa, pb]) => {
console.log("cambió A o B:", na, nb, "antes:", pa, pb);
})
);
on brilla igual dentro de un createMemo, cuando quieres un valor derivado que se recalcule solo ante ciertas fuentes e ignore otras que también lee para computar.
import { createMemo } from "solid-js";
// se recalcula solo cuando cambia filtro; lee orden sin depender de el
const vista = createMemo(
on(filtro, () => aplicar(filtro(), items(), orden()))
);
Aquí vista se recomputa cuando cambia filtro, tomando de paso los items y el orden vigentes sin suscribirse a ellos. Has elegido con precisión el único eje que gobierna la derivación.
Las deps no tienen por qué ser signals crudos: sirve cualquier accesor —un createMemo, una función derivada, o () => props.valor para escuchar una prop reactiva—. A on solo le importa recibir funciones que pueda leer en su scope de rastreo; de dónde salga el valor le es indiferente. Eso te deja fabricar disparadores compuestos: on(() => a() + b(), fn) trata la suma como una sola fuente, porque su cuerpo rastrea a y b, y dispara cuando cambie cualquiera de los dos. Envolver la dep en un createMemo con su propio equals te permite además cortar el disparo cuando el valor derivado no cambia de verdad.
flowchart LR D[deps explicitas a y b] --> T[solo estas fuentes se rastrean] B[cuerpo de on] --> U[corre sin rastrear nada mas] T --> F[cambia a o b y dispara] DF[defer true] --> SK[salta el primer run] SK --> F style T fill:#89b4fa,color:#11111b style U fill:#f38ba8,color:#11111b style F fill:#a6e3a1,color:#11111b
defer: no reacciones al montar, solo a los cambios
Por defecto, un efecto corre una vez al crearse. A veces eso no es lo que quieres: buscas un watcher puro, que se quede callado en el montaje y solo hable cuando la fuente cambie de verdad más adelante. Para eso está la opción defer.
createEffect(
on(
seleccion,
(id) => guardarPreferencia(id), // NO corre al montar
{ defer: true } // solo cuando seleccion cambie después
)
);
Con defer: true, la primera ejecución se salta por completo. El cuerpo no corre hasta que seleccion cambia por primera vez, y en esa primera reacción prevInput será undefined, porque no hubo run previo que registrara un valor. Es el equivalente exacto del watch con immediate: false de otros frameworks: reaccionar al cambio, no al estado inicial.
El precio: la lista que tú mantienes
Al declarar las deps a mano recuperas un riesgo que el rastreo automático había abolido: la dependencia olvidada. Si el cuerpo empieza a usar una fuente nueva como disparador pero no la añades a la lista, el efecto no reaccionará a ella, y no habrá error —solo una reacción que se queda corta—.
// BUG sutil: el cuerpo usa `orden`, pero solo `filtro` está en las deps
createEffect(
on(filtro, () => {
render(items(), filtro(), orden()); // cambiar `orden` NO redibuja
})
);
Cambiar orden no dispara nada, porque on solo escucha a filtro. Puede que sea exactamente lo que buscabas —orden como contexto inerte— o puede ser un despiste. La diferencia entre intención y bug no vive en el código, sino en tu cabeza, y on no puede adivinarla. Esta es la carga que aceptas al invertir el modelo: las dependencias pasan a ser tu responsabilidad, igual que las listas de useEffect en React que Solid presumía de haber jubilado. La conclusión práctica no es evitar on, sino usarlo cuando esa carga compra algo concreto —precisión sobre el disparo, o defer— y nunca por simple desconfianza del automático.
El caso canónico de defer es evitar efectos secundarios innecesarios en el arranque: no quieres persistir en disco, disparar una petición de red o registrar analítica con el valor inicial, que aún nadie eligió. Solo te interesa el cambio deliberado del usuario. Sin defer, tendrías que meter una guarda manual del tipo if (esPrimeraVez) return, ensuciando el cuerpo con estado que on ya sabe llevar por ti. defer expresa esa intención en una palabra.
Fíjate en la simetría con la lección anterior. untrack apaga el rastreo en un fragmento dentro de un cuerpo que por defecto rastrea. on hace lo inverso a escala del cuerpo entero: rastrea solo la lista de deps y deja todo lo demás sin rastrear. Puedes pensar on(deps, fn) como un efecto cuyo cuerpo está envuelto en un untrack implícito, con las deps reintroducidas a mano como las únicas suscripciones vivas. Esta dualidad es la que explota la lección 4 para lograr control fino.
El corazón de Solid es que no declaras dependencias: las descubres leyendo. Es un modelo descriptivo —dices qué quieres computar y el sistema deduce de qué depende—, y su virtud es que nunca olvidas ni sobras una dependencia, porque no las escribes. on renuncia a esa virtud a propósito, y hay que entender por qué el intercambio a veces vale la pena. Cuando declaras las deps a mano vuelves al régimen de las listas de dependencias que Solid abolió, con su riesgo clásico: si el cuerpo usa una fuente que no pusiste en la lista, no reaccionará a ella, y tú serás el único responsable de esa omisión. A cambio ganas algo que el rastreo automático no puede darte: la capacidad de decir con exactitud este cálculo depende de esto y solo de esto, aunque lea otras cosas para producirlo. Esa distinción —entre las fuentes que gobiernan cuándo ocurre algo y las que apenas informan su resultado— no existe en el modelo automático, donde toda lectura rastreada pesa igual. on la hace expresable. El coste es la carga de mantenerla: cada vez que el cuerpo empieza a depender de una fuente nueva como disparador, debes recordar añadirla a la lista, porque el sistema ya no lo hará por ti. Por eso on no es el modo por defecto ni debería serlo; es la herramienta que sacas cuando el rastreo automático arrastraría dependencias que no quieres, o cuando defer te ahorra la ejecución inicial. Usada así —con parquedad, para trazar a mano una frontera que el automático no distingue— on es precisión. Usada por costumbre, para “tener control”, es regresar voluntariamente a los bugs de dependencias que fuiste a Solid a dejar atrás. El criterio no es “control sí o no”, sino: ¿necesito distinguir disparadores de lecturas, o solo estoy desconfiando del automático sin motivo?
- Escribe un efecto con
on(a, ...)que leab()en el cuerpo y confirma que soloalo dispara, nuncab. - Usa la firma completa del callback para imprimir el valor previo y el actual de la dependencia en cada cambio.
- Convierte un
createMemonormal en uno cononpara que dependa de una sola fuente e ignore las demás que lee. - Aplica
defer: truea un efecto que persiste una preferencia y verifica que no escribe nada en el montaje, solo al cambiar. - Pasa un array
[a, b]aon, dispáralo cambiando cada uno, y observa cómo llegan los arrays de valor actual y previo.