Anatomía de un signal
El primitivo createSignal por dentro: la tupla [getter, setter], por qué el getter es una función y no un valor, los tipos Signal, Accessor y Setter, y la idea nuclear de que leer un signal dentro de un ámbito reactivo es suscribirse a él.
El par [getter, setter]
createSignal recibe un valor inicial y devuelve una tupla de exactamente dos elementos: un getter para leer el valor actual y un setter para reemplazarlo. La convención universal es desestructurar esa tupla con un nombre y ese mismo nombre prefijado con set:
import { createSignal } from "solid-js";const [count, setCount] = createSignal(0);count(); // 0 -> leer es LLAMAR a la funcionsetCount(5); // escribir un valor nuevocount(); // 5Que sea una tupla y no un objeto { get, set } es deliberado: te obliga a nombrar ambas mitades en el punto de creación y hace visible, de un vistazo, quién puede leer y quién puede escribir. Puedes entregar solo el getter a un componente hijo y guardarte el setter, logrando un valor de solo lectura sin ceremonia alguna.
const [count, setCount] = createSignal(0);const soloLectura = count; // reparte lecturas y nada mas// setCount queda encapsulado: nadie de fuera puede mutar el estadoEl getter es una función, no un valor
Aquí está la decisión de diseño que distingue a Solid de casi todo lo demás. En otros sistemas, un estado reactivo es una propiedad: lees state.count y una capa de proxies intercepta el acceso. Solid renuncia al proxy para el signal y te da una función explícita: escribes count(), con paréntesis. ¿Por qué molestarse?
Porque una lectura debe poder hacer dos cosas a la vez: devolver el valor y registrar quién lo está leyendo. Una función es el lugar natural para ese doble trabajo. Cuando llamas a count(), el getter no solo retorna el número: consulta si hay una computación reactiva activa —un efecto, un memo, el propio JSX— y, si la hay, la inscribe como observadora. Un acceso a propiedad plano no tendría dónde colgar ese comportamiento sin recurrir a proxies.
const [count, setCount] = createSignal(0);console.log(count); // [Function] <- la funcion en si, casi nunca lo que quieresconsole.log(count()); // 0 <- el valor, invocando el getterLeer es suscribirse
Esta es la frase que hay que grabar a fuego. En Solid, el acto de llamar a count() dentro de un ámbito reactivo no es una lectura pasiva: es declarar una dependencia. El signal guarda internamente el conjunto de sus observadores; la computación activa guarda la lista de sus fuentes. Leer teje ese enlace bidireccional; escribir lo recorre para notificar.
La consecuencia es que no hay lista de dependencias que declarar. No existe un array como el de useEffect; el sistema descubre las dependencias ejecutando tu código y anotando qué getters se invocaron. Si esta vez leíste count() y name(), dependes de ambos; si un if hace que la próxima vez solo leas count(), la suscripción a name se descarta automáticamente. El grafo es dinámico y siempre exacto.
Fuera de un ámbito reactivo —en el cuerpo de una función normal, en un manejador de evento— llamar a count() simplemente devuelve el valor sin suscribir a nadie. La misma función se comporta como lectura reactiva o como lectura inerte según dónde la invoques. Esa dualidad, que exploraremos en la próxima lección, es la fuente tanto del poder de Solid como de sus confusiones más típicas.