Dónde vive la reactividad
La reactividad no vive en el cuerpo del componente, sino en el JSX y en los efectos. El cuerpo solo cablea el grafo: declara nodos y conexiones que otro motor ejecutará por su cuenta.
El cuerpo cablea; el grafo ejecuta
Piensa en el cuerpo como el plano de un circuito. Dibujar el plano —ejecutar el cuerpo— ocurre una vez. La corriente que luego lo recorre —la reactividad— es otra cosa distinta y corre cada vez que cambia un signal.
function Perfil(props) { const [activo, setActivo] = createSignal(false); // el cuerpo: solo declaraciones. Nada de aqui "reacciona". const clase = () => (activo() ? "on" : "off"); // una funcion derivada return ( <div class={clase()}> <span>{props.nombre}</span> <button onClick={() => setActivo(!activo())}>toggle</button> </div> );}Lo único reactivo son las expresiones del JSX: class={clase()} y {props.nombre}. El compilador envuelve cada una en su propio efecto, que se re-ejecuta cuando cambian sus dependencias. La función clase del cuerpo es solo una declaración; no “corre” hasta que el JSX la llama dentro de un efecto. El cuerpo no reacciona: describe quién reaccionará.
El compilador convierte el JSX en efectos
Cada expresión dinámica del JSX se compila a un efecto dedicado que lee los signals que toca, se suscribe a ellos y escribe el resultado en un nodo del DOM:
Conceptualmente, lo que escribes se transforma en algo así:
// lo que escribes<span>{props.nombre}</span>// lo que genera el compilador (idea aproximada)const span = document.createElement("span");createRenderEffect(() => (span.textContent = props.nombre));Por eso no necesitas useMemo para el DOM: la granularidad ya es de una expresión por efecto. Cambiar props.nombre re-ejecuta solo ese efecto de texto y actualiza solo ese nodo, sin tocar a sus hermanos ni reconciliar árbol alguno.
Hay además dos variantes según cuándo corre el efecto. createRenderEffect se ejecuta durante el render inicial, antes de pintar; es lo que el compilador usa internamente para el DOM. createEffect corre después del montaje, y es el que empleas para efectos hacia el exterior. La distinción importa cuando necesitas leer el DOM ya construido o evitar un parpadeo.
// corre durante el render, util para escribir el DOMcreateRenderEffect(() => (el.className = clase()));// corre despues del montaje, util para efectos hacia el exteriorcreateEffect(() => analitica.enviar(pagina()));Efectos explícitos: cuándo sí
La regla es simple. Si el valor termina en el JSX, casi nunca necesitas un memo: el efecto del JSX ya lo rastrea. Reserva createMemo para derivados caros o compartidos, y createEffect para efectos que escapan del render: red, almacenamiento, suscripciones, sincronización con sistemas externos.
const [n, setN] = createSignal(2);// derivacion barata: una funcion basta, se recalcula al leerseconst doble = () => n() * 2;// derivacion cara o compartida: memoiza, se recalcula solo si n cambiaconst pesado = createMemo(() => costoso(n()));// efecto secundario: sincroniza con el mundo exteriorcreateEffect(() => localStorage.setItem("n", String(n())));doble no cachea nada: cada lectura recalcula. Da igual si es barato. pesado sí memoiza: guarda el resultado y solo lo rehace cuando n cambia, y además notifica a sus consumidores una sola vez. createEffect no devuelve valor; existe por su efecto lateral y se re-ejecuta cuando cambia cualquier signal que leas dentro.
El grafo se re-suscribe en cada ejecución
Hay un detalle que cierra el modelo: las dependencias de un efecto no se declaran, se descubren, y se vuelven a descubrir cada vez que el efecto corre. Solid recolecta como dependencias exactamente los signals que se leyeron durante esa ejecución. Una rama no tomada no suscribe nada.
const [modo, setModo] = createSignal("a");const [x, setX] = createSignal(1);const [y, setY] = createSignal(2);createEffect(() => { // se suscribe a modo, y ademas a x O a y segun el valor actual console.log(modo() === "a" ? x() : y());});Con modo en "a", el efecto lee modo y x, así que se suscribe a esos dos; y no se lee, y cambiar y no dispara nada. Cuando modo pasa a otro valor, en la siguiente corrida el grafo recolecta un conjunto distinto de dependencias. Esta suscripción dinámica es la razón última de la regla del nivel anterior: hay que leer dentro de la función, porque leer es, literalmente, suscribirse.
A veces quieres controlar eso a mano. on fija las dependencias de forma explícita y permite diferir la primera ejecución; untrack lee un signal sin suscribirse a él:
import { createEffect, on, untrack } from "solid-js";// dependencias explicitas: solo reacciona a modo, no a lo que lea dentrocreateEffect(on(modo, (m) => console.log("modo cambio a", m), { defer: true }));createEffect(() => { const actual = x(); // se suscribe a x const cfg = untrack(() => y()); // lee y SIN suscribirse console.log(actual, cfg);});Este rastreo por ejecución es lo que hace que Solid no necesite arrays de dependencias: la lista de dependencias es el conjunto de signals leídos en la última corrida, y se recalcula sola en cada una.