El componente corre una vez
El cuerpo de un componente Solid no es una función de render: es una factoría que se ejecuta una sola vez, al crear el componente. Qué significa eso y qué consecuencias arrastra en todo el modelo.
En React la función componente ES el render: se re-ejecuta en cada actualización, decenas o cientos de veces, y toda la ergonomía del framework existe para domar esa repetición. En Solid no hay bucle de render. El cuerpo del componente corre una sola vez, al crearse: es una factoría que construye el grafo reactivo y devuelve DOM real. Interiorizar este único hecho es la llave maestra del framework entero.
- Entender que el cuerpo del componente es una factoría que corre al montar, no en cada actualización.
- Distinguir con precisión la fase de creación de la fase de actualización.
- Ver qué construye el cuerpo: signals, memos, efectos y el JSX que devuelve.
- Detectar los antipatrones que asumen que el cuerpo se vuelve a ejecutar.
El cuerpo es un constructor, no un render
La forma más rápida de sentirlo es un console.log suelto en el cuerpo:
import { createSignal } from "solid-js";
function Contador() {
console.log("cuerpo ejecutado"); // se imprime UNA sola vez
const [n, setN] = createSignal(0);
return <button onClick={() => setN(n() + 1)}>{n()}</button>;
}
Pulsa el botón cien veces: cuerpo ejecutado aparece exactamente una vez. En React se imprimiría cien veces, porque la función se re-ejecuta en cada render. Aquí no. El console.log, la llamada a createSignal, la declaración de la const: todo ocurre en un único instante, el del montaje. Lo único que se actualiza con cada clic es el nodo de texto {n()}, porque el compilador de Solid lo transformó en un efecto de grano fino que se re-ejecuta por su cuenta.
Qué implica “una vez”
El ciclo de vida de un componente Solid no es un bucle, es una recta con un montaje y una propagación posterior:
flowchart TD A[crear componente] --> B[ejecutar el cuerpo una vez] B --> C[declarar signals y variables] B --> D[registrar memos y efectos] B --> E[devolver nodos DOM reales] F[un signal cambia] --> G[solo se re-ejecutan los efectos suscritos] G --> H[actualizacion quirurgica del DOM] style B fill:#89b4fa,color:#11111b style G fill:#a6e3a1,color:#11111b
De ese “una vez” se desprende casi todo lo demás:
- Una
constse calcula una sola vez. Si escribesconst doble = n() * 2obtienes un número congelado, no algo reactivo. Lo verás a fondo en el nivel de closures. - No existen los stale closures por re-render, porque no hay re-render; pero sí hay que leer los signals como funciones para que sigan vivos.
- El coste de montar se paga una vez. Las actualizaciones jamás vuelven a tocar tu lógica de creación: no hay nada que memoizar para “evitar recalcular en el próximo render”.
- Un
console.logen el cuerpo depura el montaje, no el ciclo reactivo. Para observar la reactividad hay que mirar dentro de los efectos.
onMount, onCleanup y el owner
El cuerpo se ejecuta dentro de un owner: un ámbito reactivo que Solid usa para rastrear pertenencia y limpieza. onMount registra una llamada que corre una vez tras el primer render, cuando el DOM ya existe. onCleanup registra la limpieza y puede invocarse en cualquier punto de un owner, incluso dentro de un efecto.
import { createSignal, onMount, onCleanup } from "solid-js";
function Reloj() {
const [hora, setHora] = createSignal(new Date());
onMount(() => {
const id = setInterval(() => setHora(new Date()), 1000);
onCleanup(() => clearInterval(id)); // corre al desmontar
});
return <time>{hora().toLocaleTimeString()}</time>;
}
No hay array de dependencias ni regla de exhaustive-deps que vigilar: la creación ocurre una vez y la limpieza es explícita. El intervalo queda atado al ciclo de vida del owner, y cuando el componente se desmonta, onCleanup lo detiene sin que tú tengas que acordarte en cada render.
Cada componente crea un owner hijo del owner de su padre, formando un árbol paralelo al del DOM. Cuando un nodo se desmonta, Solid recorre su subárbol de owners ejecutando los onCleanup de abajo arriba. Por eso la limpieza es automática y ordenada: no depende de que tú la orquestes en cada render, sino de la estructura del árbol de propietarios.
No hay reglas de hooks
Como el cuerpo corre una vez y createSignal es solo una función que devuelve un par getter/setter, Solid no arrastra las “reglas de los hooks” de React. Puedes crear primitivos dentro de un if, de un bucle o después de una condición, porque no dependen de un orden de llamada estable entre renders: no hay renders que alinear.
function Panel(props) {
const columnas = [];
// legal en Solid: crear signals en un bucle, prohibido en React
for (const clave of props.claves) {
columnas.push([clave, createSignal(0)]);
}
return (
<For each={columnas}>
{([clave, [valor]]) => <span>{clave}: {valor()}</span>}
</For>
);
}
Lo que sí permanece es la disciplina del owner: los primitivos creados durante el cuerpo pertenecen al owner del componente y se destruyen con él. Si creas un efecto fuera de un owner —por ejemplo, dentro de un setTimeout— queda huérfano y no se limpia solo; para eso existe createRoot, que verás más adelante. La ausencia de reglas de orden es consecuencia directa de que el cuerpo no se repite: el orden solo importaría si hubiera un segundo pase, y no lo hay.
La distancia con React se ve mejor en la consola. Allí el cuerpo es la función de render y se re-ejecuta en cada actualización; aquí, jamás:
// React: el cuerpo corre en CADA render
function ContadorReact() {
console.log("render"); // se imprime en cada actualizacion
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
}
Un vistazo de conjunto a qué se ejecuta y cuándo:
Corre una vez
El cuerpo, las const, y las llamadas a createSignal, createEffect y createMemo: se ejecutan al montar y no vuelven.
Corre en cada cambio
El callback de cada efecto y cada expresión del JSX: se re-ejecutan cuando cambia un signal que leen.
Corre al desmontar
Los onCleanup registrados en el owner: liberan intervalos, listeners y suscripciones.
No existe
El re-render del componente: no hay bucle que repita el cuerpo ni virtual DOM que reconciliar.
Que el cuerpo corra una vez no te autoriza a hacer trabajo síncrono pesado en él: bloquearías el montaje. Pero sí te libera de la ansiedad de React por “no recrear en cada render”, porque no hay siguiente render. Un objeto, un new Map() o una suscripción creados en el cuerpo se crean una sola vez; no necesitas useMemo ni useRef para estabilizarlos, porque nada los va a recrear.
Todo el modelo mental de Solid cabe en una distinción: crear frente a actualizar. El cuerpo es la fase de creación; corre una vez y su trabajo es cablear el grafo —declarar signals, derivar memos, registrar efectos— y devolver el DOM. La fase de actualización no toca jamás el cuerpo: vive dentro de los efectos y de las expresiones del JSX que Solid compiló a suscripciones de grano fino. React funde ambas fases en una sola función que corre una y otra vez, y eso te obliga a memoizar, a temer los re-renders y a controlar dependencias con listas. Solid las separa físicamente: lo que escribes una vez, corre una vez; lo que debe reaccionar, reacciona por su cuenta. Cuando interiorizas esto, código que parecía frágil en React —crear un objeto en el cuerpo, suscribirte a un evento, abrir un websocket— se vuelve trivial en Solid: ocurre una sola vez, exactamente donde lo escribiste, y se limpia cuando muere su owner.
- Escribe un componente con
console.log("montado")en el cuerpo e incrementa un signal desde un botón. Confirma en consola que solo se imprime una vez pese a los clics. - Añade
const etiqueta = "clics: " + n()y muéstrala en el JSX junto a{n()}. Observa queetiquetase queda congelada mientras{n()}sí cambia; anota por qué (lo resolverás en el nivel de closures). - Convierte un
setIntervalen reactivo cononMountmásonCleanupy comprueba que al desmontar el componente el intervalo se detiene. - Explica en una sola frase la diferencia entre la fase de creación y la de actualización en Solid.
- Pon un
console.logdentro de uncreateEffecty otro suelto en el cuerpo; observa cuál se repite al cambiar un signal y cuál no.