createStore: un proxy reactivo para árboles
createStore envuelve un objeto anidado en un proxy de solo lectura donde cada propiedad se comporta como su propio signal creado de forma perezosa. Leer una ruta como estado.usuario.nombre dentro de un scope reactivo suscribe solo a esa hoja, y escribir por ruta con setEstado notifica únicamente a quien la lee. Es la primitiva de Solid para estado con forma.
El proxy que envuelve la estructura
createStore recibe un objeto inicial y devuelve una tupla [estado, setEstado], idéntica en forma a la de createSignal. Pero el primer elemento no es tu objeto: es un proxy de solo lectura que lo recubre. Cada vez que accedes a una propiedad cuyo valor es a su vez un objeto o un array, el proxy devuelve otro proxy, de manera perezosa y recursiva. El árbol completo queda cubierto por una malla de proxies que interceptan cada lectura y cada escritura.
import { createStore } from "solid-js/store";const [estado, setEstado] = createStore({ usuario: { nombre: "Ada", edad: 36 }, preferencias: { tema: "oscuro" },});estado.usuario.nombre; // "Ada" — atraviesa dos proxies hasta la hojaEse recubrimiento no es gratuito conceptualmente, pero sí barato en la práctica: los proxies internos no se crean todos por adelantado, sino la primera vez que alguien accede a la rama que los contiene. Un store con mil ramas que nadie mira no instala mil proxies; instala solo los del camino que de verdad recorres.
La primera consecuencia práctica es que el proxy es de solo lectura. No puedes asignar estado.usuario.nombre = "Grace" directamente: en desarrollo, Solid te avisa de que estás mutando un store por fuera. Toda escritura pasa por setEstado, y esa asimetría deliberada —lectura directa, escritura por función— es la que le permite al store saber exactamente qué cambió y a quién avisar.
Cada hoja es un signal perezoso
La idea central es esta: dentro del proxy, cada propiedad se comporta como su propio signal. No hay un único signal para todo el objeto, sino un enjambre de signals, uno por hoja, todos creados de forma perezosa. La primera vez que lees estado.usuario.nombre dentro de un scope reactivo, el store instala el signal de esa hoja y te suscribe a él. Las hojas que nadie lee nunca llegan a costar un signal.
import { createEffect } from "solid-js";createEffect(() => { console.log(estado.usuario.nombre); // suscribe SOLO a usuario.nombre});setEstado("usuario", "edad", 37); // el efecto no se entera: leyó nombre, no edadsetEstado("usuario", "nombre", "Grace"); // el efecto sí corre: cambió su hojaEste es el corazón de la reactividad de grano fino sobre estructuras. En un signal que guardara el mismo objeto, cambiar edad reemplazaría el objeto entero y notificaría a todo el que leyera cualquier campo. En el store, edad y nombre son dependencias distintas: tocar una deja la otra en completo silencio.
Cada hoja usa además la igualdad por defecto de Solid, ===. Reescribir una hoja con el valor que ya tenía es un no-op silencioso: el store compara, ve que no cambió y no notifica a nadie. Esto te ahorra propagaciones inútiles sin que hagas nada especial, y de paso explica un caso desconcertante —mutar por dentro un objeto que ya vive en una hoja y reasignarlo tampoco notifica, porque le entregas la misma referencia.
setEstado("usuario", "nombre", "Grace");setEstado("usuario", "nombre", "Grace"); // no-op: la hoja no cambió, nadie despiertaConviene subrayar el matiz de dentro de un scope reactivo. Leer estado.usuario.nombre en el cuerpo suelto de una función, fuera de todo efecto, memo o JSX, devuelve el valor pero no suscribe a nada, porque no hay ningún oyente que registrar. La suscripción solo ocurre cuando la lectura sucede bajo un cómputo que rastrea. Es la misma regla que gobierna a los signals; el store no la cambia, solo la extiende a cada hoja del árbol.
Escribir por ruta con setEstado
setEstado acepta una ruta —una secuencia de claves— y, como último argumento, el nuevo valor o un actualizador. En lugar de reconstruir cada nivel con spread, nombras el camino a la hoja y Solid escribe solo ahí, reconstruyendo internamente el trayecto y notificando a los lectores de esa rama.
// Valor directo en la hojasetEstado("usuario", "nombre", "Grace");// Actualizador funcional: recibe el valor previo de esa hojasetEstado("usuario", "edad", (n) => n + 1);// Objeto en una rama: merge SUPERFICIAL de esas clavessetEstado("usuario", { nombre: "Grace", edad: 37 });// Objeto en la raíz: merge superficial de las claves de primer nivelsetEstado({ preferencias: { tema: "claro" } });La ruta no está limitada a claves literales: cualquier segmento puede ser una variable calculada en runtime —setEstado(rama, campo, v)— de modo que una función genérica de actualización puede escribir en cualquier parte del árbol sin conocerla de antemano. Es la misma potencia que hace del store el recipiente natural para estado con claves dinámicas, un caso que veremos con detalle en las lecciones siguientes.
Hay dos comportamientos que no debes confundir. Cuando el último argumento es un valor primitivo o una función, se asigna o se deriva esa hoja concreta. Cuando es un objeto, Solid hace un merge superficial de sus claves en el nivel indicado: setEstado("usuario", { edad: 37 }) cambia edad y deja nombre intacto, en lugar de reemplazar el objeto usuario entero. Esa diferencia —merge de objeto frente a reemplazo de hoja— es de las primeras cosas que hay que interiorizar del store, porque de ella depende que no borres campos sin querer.
Desenvolver el store: del proxy al objeto crudo
A veces necesitas el objeto plano que vive bajo el proxy: para serializarlo, para entregárselo a una librería ajena a Solid, o para depurar sin la capa reactiva de por medio. Esa es la función de unwrap, que te devuelve la estructura original —sin proxies, sin suscribir a nada.
import { unwrap } from "solid-js/store";const plano = unwrap(estado); // objeto crudo, sin proxies ni reactividadJSON.stringify(unwrap(estado)); // seguro para volcar el estado tal cualDos cautelas acompañan a unwrap. La primera: el objeto que devuelve es el estado real, no una copia, así que mutarlo por fuera corrompe el store a espaldas del sistema —léelo, no lo escribas. La segunda: unwrap no es reactivo; leerlo dentro de un efecto no suscribe a las hojas, porque justamente esquiva el proxy que las rastrea. Es la herramienta del borde de tu aplicación, donde el dato sale de Solid, no del interior del grafo reactivo.