wandres.dev
EL MODELO DE EJECUCIÓN · el componente corre una vez

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.

⏱ 9 min

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.

🎯 Al terminar esta lección sabrás
  • 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 const se calcula una sola vez. Si escribes const doble = n() * 2 obtienes 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.log en 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.

ℹ️
El owner es un árbol

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.

💡
Crear una vez te libera, no te limita

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.

Separa crear de actualizar

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.

⚔️ Confirma que corre una vez
  1. 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.
  2. Añade const etiqueta = "clics: " + n() y muéstrala en el JSX junto a {n()}. Observa que etiqueta se queda congelada mientras {n()} sí cambia; anota por qué (lo resolverás en el nivel de closures).
  3. Convierte un setInterval en reactivo con onMount más onCleanup y comprueba que al desmontar el componente el intervalo se detiene.
  4. Explica en una sola frase la diferencia entre la fase de creación y la de actualización en Solid.
  5. Pon un console.log dentro de un createEffect y otro suelto en el cuerpo; observa cuál se repite al cambiar un signal y cuál no.