Saltar al contenido

onCleanup: registrar la limpieza

onCleanup empuja una función de limpieza a la lista del owner actual, y Solid la dispara en dos momentos: justo antes de que la computación se re-ejecute y cuando su dueño se dispone. El teardown simétrico del setup.

Qué registra y dónde

onCleanup(fn) toma una función y la añade a la lista de limpiezas del owner activo en ese instante. Eso es todo lo que hace al llamarla: no ejecuta nada, solo agenda. La ejecución llega después, cuando el owner lo decide. Puedes llamarla en cualquier punto de un owner —en el cuerpo de un componente, dentro de un efecto, incluso dentro de una función auxiliar— siempre que sea de forma síncrona, porque lee la variable del owner actual, que solo está fijada durante la ejecución síncrona.

TSX
import { createEffect, onCleanup } from "solid-js";createEffect(() => {  const id = setInterval(() => tick(), 1000);  onCleanup(() => clearInterval(id));   // se agenda en el owner del efecto});

Puedes registrar varias limpiezas en el mismo owner —una por recurso— y todas se ejecutarán al llegar el momento, en orden inverso al de registro. La regla de oro es la proximidad: escribe cada onCleanup pegado a la línea que abre el recurso, para que apertura y cierre se lean juntos y ninguno se olvide.

Los dos momentos

Solid dispara las limpiezas de un owner en dos situaciones, y conviene tenerlas nítidas:

El primer momento es la re-ejecución. Cuando una computación que se repite —un efecto o un memo— va a correr de nuevo porque cambió una de sus dependencias, Solid ejecuta antes las limpiezas registradas en la corrida anterior. Así, si el efecto abrió un socket para el canal a y ahora el canal pasa a b, primero se cierra el socket de a y solo entonces el cuerpo abre el de b. Nunca hay dos abiertos ni uno huérfano.

El segundo momento es la disposición. Cuando el owner se destruye —porque el componente sale del DOM o porque se llamó a dispose—, se ejecuta la última tanda de limpiezas pendientes. Es el desmontaje final: lo que quedara abierto en la última corrida se cierra aquí.

TSX
createEffect(() => {  const canal = suscripcion();  const socket = abrir(canal);  onCleanup(() => socket.cerrar());  // corre antes de cada rerun Y al disponer});

En un onMount —un efecto que corre una sola vez y no rastrea nada— solo existe el segundo momento: la limpieza que registres dentro correrá únicamente al desmontar, porque nunca hay una segunda corrida.

Dónde se engancha según dónde lo llames

onCleanup siempre se engancha al owner activo, y ese owner cambia según el lugar de la llamada. Si lo pones en el cuerpo del componente, se engancha al owner del componente, que solo se dispone una vez, al desmontar; por eso ahí la limpieza corre una sola vez. Si lo pones dentro de un efecto que se repite, se engancha al owner del efecto, que se re-crea en cada corrida; por eso ahí la limpieza corre antes de cada rerun y también al final.

TSX
import { createEffect, onCleanup } from "solid-js";function Widget() {  // owner del componente: corre solo al desmontar  onCleanup(() => console.log("adios Widget"));  createEffect(() => {    const id = abrir(canal());    // owner del efecto: corre en cada cambio de canal y al desmontar    onCleanup(() => cerrar(id));  });  return <div />;}

La misma primitiva significa “al desmontar” en el cuerpo y “antes de cada rerun” dentro de un efecto, sin cambiar de nombre, porque el cuándo no lo decide onCleanup: lo decide el owner al que se engancha. Es la consecuencia directa de que cada computación sea su propio owner.

Una consecuencia práctica y potente: puedes envolver un addEventListener con su onCleanup dentro de una función auxiliar reutilizable —una primitiva propia— y llamarla desde cualquier componente. La limpieza se enganchará al owner de quien la invoque, sin que la función necesite saber nada de él.

TypeScript
import { onCleanup } from "solid-js";// primitiva reutilizable: se engancha al owner de quien la llamefunction usarEvento(target, tipo, handler) {  target.addEventListener(tipo, handler);  onCleanup(() => target.removeEventListener(tipo, handler));}

Los casos típicos

Casi todo lo que “vive” fuera del grafo reactivo necesita una limpieza. Estos son los sospechosos habituales:

El patrón de una petición cancelable ilustra la simetría perfecta entre abrir y cerrar:

TSX
import { createEffect, onCleanup } from "solid-js";createEffect(() => {  const ctrl = new AbortController();  fetch(`/api/${id()}`, { signal: ctrl.signal })    .then((r) => r.json())    .then(aplicar)    .catch(ignorarAbortadas);  onCleanup(() => ctrl.abort());   // cancela la peticion vieja al cambiar id});

Al cambiar id, la limpieza aborta la petición en curso antes de lanzar la nueva: no llegan respuestas fuera de orden ni se aplica una respuesta obsoleta. Lo mismo vale para librerías externas —un gráfico, un mapa, un editor—: si su constructor reserva algo, su método destroy o dispose va dentro de un onCleanup.