Saltar al contenido

Signals fuera de componentes

Definir signals a nivel de módulo para compartir estado global sin librerías: por qué funciona (los signals no necesitan owner, solo las computaciones lo necesitan), el patrón de módulo con acciones, su ciclo de vida sin disposición y el peligro del estado compartido entre peticiones en SSR.

Estado compartido en un módulo

Un signal creado en la cima de un módulo existe una sola vez para todo el programa. Cualquier componente que importe su getter y lo lea en su JSX quedará suscrito; cualquiera que llame al setter actualizará a todos a la vez. Es un bus de estado global de una sencillez casi ofensiva:

TSX
// store/contador.tsimport { createSignal } from "solid-js";const [count, setCount] = createSignal(0);export { count };                                  // lectura publicaexport const incrementar = () => setCount(c => c + 1);export const reiniciar = () => setCount(0);
TSX
// cualquier componente, en cualquier parte del arbolimport { count, incrementar } from "../store/contador";function Boton() {  return <button onClick={incrementar}>Total: {count()}</button>;}

Fíjate en el patrón: exportas el getter para leer y unas acciones con nombre para escribir, pero te guardas el setter dentro del módulo. Nadie de fuera puede mutar el estado de formas no previstas; toda escritura pasa por una función con intención explícita. Es encapsulación clásica, lograda sin clases ni ceremonia.

Por qué funciona: quién necesita un owner

Aquí conviene una distinción fina que casi nadie explica. En Solid, las computaciones —efectos y memos— viven dentro de un árbol de ownership: alguien es su dueño y se encargará de limpiarlas con onCleanup cuando ese dueño se destruya. Los signals, en cambio, no necesitan dueño: son solo un valor con una lista de observadores. Por eso puedes crear un signal en cualquier parte, incluido el nivel de módulo, sin envolverlo en nada.

La contrapartida aparece si intentas crear una computación a nivel de módulo. Un createEffect o un createMemo fuera de todo componente no tiene dueño que lo limpie, y Solid te avisará por consola de que una computación creada fuera de una raíz nunca se dispondrá. La solución es envolver ese estado reactivo global en createRoot, que crea un dueño de larga vida a propósito:

TSX
import { createRoot, createMemo } from "solid-js";// Un memo global necesita una raiz que lo posea:const dobleGlobal = createRoot(() => createMemo(() => count() * 2));

Ciclo de vida: viven mientras viva la app

Un signal de módulo no se crea ni se destruye con los componentes: nace la primera vez que se importa el módulo y vive hasta que el programa termina. No tiene onCleanup, no participa en ningún ciclo de montaje. Esto es exactamente lo que quieres para estado verdaderamente global —el usuario autenticado, el tema, un carrito— y exactamente lo que no quieres para estado efímero de una vista.