wandres.dev
CREATEEFFECT · reacciones a los signals

El valor previo y la limpieza

El efecto puede pasarse un valor a sí mismo entre ejecuciones con createEffect prev e inicial, y liberar lo que reservó con onCleanup antes de cada rerun y al destruirse el dueño. Setup y teardown simétricos.

⏱ 14 min

Un efecto no siempre vive en un eterno presente. A veces necesita recordar en qué estaba la vez anterior —para comparar, acumular o guardar un identificador—, y casi siempre necesita deshacer lo que hizo antes de volver a hacerlo. Solid cubre ambas necesidades con dos piezas minúsculas: el valor previo que el efecto se pasa a sí mismo, y onCleanup, el mecanismo que convierte cada efecto en un par simétrico de montaje y desmontaje. Juntos transforman el efecto de un disparo suelto en un ciclo de vida completo.

🎯 Al terminar esta lección sabrás
  • Usar el valor previo con createEffect, pasándole una función que recibe prev y un inicial.
  • Encadenar ejecuciones comparando o acumulando a través de ese prev.
  • Registrar limpieza con onCleanup dentro del efecto para soltar recursos.
  • Entender el orden: la limpieza corre antes del siguiente run y al destruir el dueño.

El valor que el efecto se pasa a sí mismo

createEffect acepta un segundo argumento: un valor inicial. Cuando lo das, la función del efecto recibe como parámetro el valor que devolvió la ejecución anterior; en el primer run recibe el inicial.

Así el efecto puede hilar una ejecución con la siguiente sin necesidad de un signal externo que haga de memoria.

import { createSignal, createEffect } from "solid-js";

const [n, setN] = createSignal(0);

createEffect((anterior) => {
  const actual = n();
  if (anterior !== actual) {
    console.log(`cambio de ${anterior} a ${actual}`);
  }
  return actual; // se convierte en el `anterior` del proximo run
}, n());

Es el patrón del acumulador. El retorno del efecto no lo consume el grafo —recuerda que un efecto es una hoja—, pero sí lo consume el propio efecto en su siguiente iteración.

Sirve para detectar la dirección de un cambio, mantener un máximo histórico, o conservar entre reruns un objeto costoso de recrear.

createEffect((maxPrevio) => {
  const v = valor();
  return v > maxPrevio ? v : maxPrevio; // maximo sostenido sin signal extra
}, -Infinity);

onCleanup: soltar lo que reservaste

La mayoría de los efectos que tocan el mundo real reservan algo: un temporizador, un listener del DOM, una suscripción, un socket. Si el efecto vuelve a correr o el componente desaparece, ese recurso debe liberarse, o tendrás una fuga.

onCleanup registra una función de limpieza que Solid ejecutará en dos momentos: justo antes de la siguiente ejecución del efecto, y cuando el dueño del efecto se destruye.

import { createEffect, onCleanup } from "solid-js";

createEffect(() => {
  const id = canal();
  const socket = abrirSocket(id);

  onCleanup(() => socket.cerrar());
});

Sigue la coreografía cuando canal cambia de a a b. Solid ejecuta primero la limpieza registrada en el run anterior —cierra el socket de a—, y solo entonces corre de nuevo el cuerpo, que abre el socket de b y registra una nueva limpieza.

Cuando el componente se desmonta, se ejecuta la última limpieza pendiente y el socket de b se cierra también. En ningún momento hay dos sockets abiertos ni uno huérfano.

flowchart LR
R1[run abre recurso A] --> D[cambia una dependencia]
D --> C1[cleanup cierra A]
C1 --> R2[run abre recurso B]
R2 --> X[se destruye el dueno]
X --> C2[cleanup cierra B]
style R1 fill:#a6e3a1,color:#11111b
style R2 fill:#a6e3a1,color:#11111b
style C1 fill:#f38ba8,color:#11111b
style C2 fill:#f38ba8,color:#11111b

El caso más frecuente es un listener del DOM o un intervalo. El patrón es siempre el mismo: abre en el cuerpo, cierra en el onCleanup inmediato.

createEffect(() => {
  const onScroll = () => setY(window.scrollY);
  window.addEventListener("scroll", onScroll);
  onCleanup(() => window.removeEventListener("scroll", onScroll));
});

Puedes registrar varias limpiezas en un mismo efecto —una por cada recurso—, y todas se ejecutan al llegar el momento. La regla de oro es la simetría: cada cosa que abres, ciérrala en su onCleanup, lo más cerca posible de donde la abriste. Así el montaje y el desmontaje se leen juntos y ninguno se olvida.

💡
prev o un signal aparte

Usa el valor previo cuando lo que quieres recordar es privado del efecto y solo sirve para comparar con la ejecución siguiente: un máximo, el valor anterior, un handle. Si en cambio ese dato debe ser leído por otras partes de la interfaz, no lo escondas en el prev: ponlo en un signal o, mejor, deriva un createMemo. El prev es memoria interna del efecto, no estado compartido.

Un ciclo de vida atado al dueño

onCleanup no funciona por magia global: se apoya en el árbol de ownership de Solid. Todo efecto tiene un dueño —el componente, el createRoot o el efecto padre en cuyo contexto se creó—, y su limpieza queda enganchada a ese dueño.

Cuando el dueño se destruye, Solid recorre a sus hijos y ejecuta todas sus limpiezas en cascada. No hay que desmontar nada a mano: la disposición es automática y determinista.

Esto es lo que hace que un efecto sea un ciclo de vida completo y no un disparo suelto. El primer run es el montaje; cada rerun es un remontaje precedido de su limpieza; la destrucción del dueño es el desmontaje final. Todo el arco vive en un solo bloque, junto a la lógica que lo motiva, en lugar de repartido entre un onMount y un onUnmount que hay que mantener sincronizados.

La limpieza es la otra mitad de la reacción

Es tentador ver onCleanup como un apéndice —lo que se pone al final para no dejar cabos sueltos—, pero es exactamente la mitad simétrica de lo que un efecto significa. Un efecto declara una correspondencia entre un estado reactivo y un efecto en el mundo: mientras canal valga a, debe existir un socket a a. Mantener una correspondencia a lo largo del tiempo no es solo establecerla cuando el estado aparece, sino deshacerla cuando el estado cambia o desaparece; si solo abres y nunca cierras, no estás manteniendo una correspondencia, estás acumulando basura. Por eso Solid ata la limpieza al mismo ciclo que la ejecución y al mismo árbol de ownership que gobierna la vida de todo lo demás: la creación y la destrucción de un recurso son el mismo hecho reactivo visto desde sus dos extremos. Cuando escribas un efecto que toca el exterior, no pienses “qué hago cuando esto cambia” sino “qué relación quiero sostener, y cómo se establece y se rompe”. El cuerpo del efecto sostiene; el onCleanup rompe. Con esa pareja, la gestión de recursos deja de ser una lista de tareas que recordar y se convierte en una propiedad estructural: cada efecto es responsable de limpiar exactamente lo que ensucia, y el árbol de dueños garantiza que nada sobreviva a quien lo creó.

⚔️ Sostén una correspondencia en el tiempo
  1. Escribe un efecto con valor inicial que informe si un contador subió o bajó respecto a la ejecución anterior, usando solo prev.
  2. Monta un setInterval dentro de un efecto y libéralo con onCleanup; cambia la dependencia y confirma que el intervalo viejo muere antes de nacer el nuevo.
  3. Añade un listener de scroll con su limpieza y comprueba que se retira al desmontar el componente.
  4. Registra dos onCleanup en el mismo efecto para dos recursos distintos y verifica que ambos se ejecutan.