Lectura reactiva vs no reactiva
El mismo count() se comporta de dos maneras según dónde lo llames: dentro de un ámbito de rastreo se suscribe y reacciona; fuera de él es una lectura inerte. Por qué guardar el valor en una variable congela la reactividad, y por qué en Solid se pasa la función, no el valor.
El getter de un signal tiene una doble personalidad: la misma llamada, count(), unas veces teje una suscripción viva y otras es una simple lectura que se olvida al instante. La diferencia no está en el signal, sino en el lugar desde el que lo invocas. Dominar dónde una lectura es reactiva y dónde no lo es separa a quien pelea con Solid de quien lo entiende.
- Distinguir un ámbito de rastreo (JSX, efecto, memo) de un contexto inerte.
- Ver por qué guardar
count()en una variable congela su valor. - Entender la relación entre “el componente corre una vez” y las lecturas.
- Aprender a pasar la función
counten vez del valorcount()para conservar la reactividad.
El mismo count(), dos comportamientos
Un ámbito de rastreo es cualquier lugar donde Solid está ejecutando una computación reactiva y, por tanto, tomando nota de qué signals se leen: el cuerpo de un createEffect, el de un createMemo y las expresiones de tu JSX. Llamar a count() ahí dentro suscribe esa computación al signal. En cambio, llamar a count() en un manejador de evento, en un setTimeout o en una función utilitaria cualquiera es una lectura inerte: te da el valor del momento y no suscribe a nadie.
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("reactivo:", count()); // se re-ejecuta con cada cambio
});
function alHacerClic() {
console.log("inerte:", count()); // lee una vez, no re-suscribe
setCount(count() + 1);
}
El efecto se volverá a ejecutar cada vez que count cambie, porque su lectura ocurrió dentro de un ámbito de rastreo. El console.log del manejador solo se dispara cuando alguien hace clic: su lectura de count() no crea dependencia alguna.
El pecado de guardar el valor en una variable
Recuerda el axioma de Solid: el cuerpo de un componente se ejecuta una sola vez. De ahí se deduce una trampa mortal. Si en la cima del componente escribes const valor = count(), estás leyendo el signal una vez, en el instante de la creación, y guardando ese número en una variable normal. La variable no es reactiva; es una foto. Cuando count cambie después, valor seguirá anclado a su valor inicial para siempre.
function Contador() {
const [count, setCount] = createSignal(0);
const valor = count(); // FOTO: se evalua una vez, vale 0 para siempre
return (
<>
<p>Roto: {valor}</p> {/* nunca cambia */}
<p>Vivo: {count()}</p> {/* se actualiza con cada setCount */}
<button onClick={() => setCount(count() + 1)}>+1</button>
</>
);
}
La regla práctica es incómoda para quien viene de React, donde el componente re-renderiza y const valor = ... se recalcula en cada pasada: en Solid, la llamada al getter debe vivir en el punto exacto del JSX donde se usa el valor. La expresión <p>{count()}</p> funciona porque Solid la re-ejecuta quirúrgicamente; <p>{valor}</p> no, porque valor ya es un número muerto antes de llegar ahí.
La solución a la foto no es “recalcular”, es diferir la lectura. En vez de const doble = count() * 2 (una foto), escribe const doble = () => count() * 2 (una función). Ahora doble es una señal derivada: cada vez que la llames dentro del JSX leerá count() fresco y se suscribirá. Has convertido un valor congelado en una lectura reactiva con solo anteponer () =>.
Pasar la función, no el valor
La misma lógica gobierna cómo compartes estado entre componentes. Si le pasas a un hijo count(), le entregas un número muerto; el hijo no tiene forma de enterarse de cambios futuros. Si le pasas count —la función—, le das una lectura reactiva que puede invocar dentro de su JSX y suscribirse él mismo.
// MAL: el hijo recibe un numero congelado
<Saludo veces={count()} />
// BIEN: el hijo recibe el getter y decide cuando leer
<Saludo veces={count} />
flowchart TD
A[Necesito compartir estado reactivo] --> B{Paso el valor o la funcion}
B -->|count con parentesis| C[Foto muerta: no reacciona]
B -->|count sin parentesis| D[Getter vivo: el consumidor se suscribe]Existe también el camino inverso: a veces quieres leer un signal sin suscribirte, incluso dentro de un efecto. Para eso está untrack, que ejecuta una función y descarta cualquier suscripción que ocurra dentro. Es la herramienta para decir “quiero el valor actual de esto, pero no me re-ejecutes cuando cambie”.
import { untrack } from "solid-js";
createEffect(() => {
const a = count(); // dependencia
const b = untrack(() => other()); // lectura sin dependencia
console.log(a, b); // se re-ejecuta con count, no con other
});
El malentendido que más tiempo cuesta desmontar al llegar a Solid es creer que un signal “es reactivo” como quien dice que un número es par. No lo es: la reactividad no reside en el signal, reside en el acto de leerlo dentro de un ámbito que está rastreando. El mismísimo count() es una suscripción viva en el JSX y una lectura desechable en un setTimeout, y nada del signal cambió entre un caso y otro; cambió el contexto de ejecución. Por eso guardar el resultado en una variable rompe la magia: una variable es un valor, y un valor no tiene contexto ni sabe rastrear; captura el número y tira el hilo que lo conectaba a su fuente. Y por eso, simétricamente, la solución siempre es la misma —retrasar la lectura envolviéndola en una función— porque una función sí arrastra consigo el momento de su invocación, y ese momento puede volver a ocurrir dentro de un ámbito de rastreo. Cuando de verdad interiorizas que pasas funciones y no valores, que la última llamada al getter debe hacerse lo más tarde posible, justo en el punto donde el DOM consume el dato, entonces dejas de escribir bugs de “no se actualiza” y empiezas a pensar como piensa el grafo: no en estados que se copian, sino en lecturas que se re-ejecutan. Solid no te pide que recuerdes actualizar; te pide que no interrumpas el cable entre la fuente y el consumidor. Toda la disciplina se reduce a eso: no cortes el cable guardando el valor demasiado pronto.
- Crea un componente con
const valor = count()en la cima y muéstralo junto a{count()}; pulsa un botón que incremente y observa cuál se mueve y cuál no. - Arregla la versión rota convirtiendo
valoren una señal derivada con() =>y confirma que ahora reacciona. - Pasa a un componente hijo primero
count()y luegocount; comprueba en cuál de los dos el hijo se actualiza. - Dentro de un
createEffectque lee dos signals, envuelve uno enuntracky verifica que el efecto ya solo reacciona al otro. - Explica con tus palabras por qué “el componente corre una vez” hace que guardar el valor sea especialmente peligroso.