Saltar al contenido

createMutable: el proxy de escritura directa

createMutable devuelve un único objeto proxy que puedes mutar en sitio: asignar a state.x = 1 es, por sí mismo, una operación reactiva, sin setter ni tupla. Cómo comparte el motor de rastreo por propiedad de createStore pero invierte su contrato —de escritura canalizada a escritura libre—, qué envuelve en profundidad, y por qué esa comodidad aparente reorganiza quién es dueño del cambio.

Un único proxy, sin setter

createStore te entrega dos cosas: un proxy de solo lectura y una función setStore que es la única puerta legítima para escribir. createMutable colapsa esas dos cosas en una: un proxy que es a la vez la ventana de lectura y la superficie de escritura. Su firma lo delata —createMutable<T>(state: T): T—: entra un objeto, sale un objeto del mismo tipo, y no hay segundo elemento en el retorno porque no hay canal separado.

TSX
import { createMutable } from "solid-js/store";const state = createMutable({ contador: 0, nombre: "Ada" });// leer: como cualquier propiedadstate.contador; // 0// escribir: asignacion directa, sin setterstate.contador = 1;state.nombre = "Grace";

No hay setContador, no hay ruta con comillas, no hay actualizador funcional. Escribes como escribirías sobre un objeto plano de JavaScript. La diferencia invisible es que ese objeto no es plano: es un proxy que intercepta cada get para registrar dependencias y cada set para notificarlas. Y ahí está el corazón del primitivo: la asignación misma es el disparador.

Aquí está el detalle que lo cambia todo. En un objeto normal, obj.x = 1 es una operación muda: cambia un valor y nadie se entera. En un createMutable, esa misma línea atraviesa el set del proxy, que compara el valor nuevo con el viejo y, si difiere, notifica a todos los que hayan leído state.x dentro de un ámbito de rastreo. La asignación es el disparador.

TSX
import { createEffect } from "solid-js";const state = createMutable({ contador: 0 });createEffect(() => {  console.log("contador:", state.contador); // se suscribe al leer});state.contador = 1; // el efecto vuelve a correr: imprime 1state.contador = 2; // corre otra vez: imprime 2

El pacto de rastreo es idéntico al de todo Solid —leer dentro de un ámbito reactivo suscribe— pero la escritura ha cambiado de forma. Ya no invocas una función que declara tu intención de cambio; ejecutas una mutación y el proxy la traduce en notificación por ti. Leer sigue siendo leer; escribir ha dejado de ser una llamada para volverse un efecto secundario de la asignación.

Mismo motor, contrato invertido

createMutable y createStore no son parientes lejanos: son la misma máquina de rastreo por propiedad con la superficie de escritura invertida. En el store, mutar el proxy directamente es un error —en desarrollo, Solid te avisa— porque toda escritura debe pasar por setStore para ser rastreada. En el mutable, esa prohibición se levanta: el proxy es escribible, y la ruta canalizada desaparece.

TSX
// createStore: escritura canalizada por setStoreimport { createStore } from "solid-js/store";const [store, setStore] = createStore({ contador: 0 });setStore("contador", 1);          // unica via legitima// store.contador = 1;            // en dev, Solid advierte: no rastreado// createMutable: escritura libre sobre el proxyimport { createMutable } from "solid-js/store";const mut = createMutable({ contador: 0 });mut.contador = 1;                 // esta ES la via

La consecuencia va más allá de la sintaxis. En el store, el conjunto de lugares donde el estado puede cambiar es finito y localizable: busca setStore y tendrás el censo completo de las escrituras. En el mutable, cualquier fragmento de código que tenga una referencia al objeto puede reasignar cualquier propiedad, en cualquier momento. La escritura deja de ser un evento con nombre propio para disolverse en el flujo imperativo del programa. Es la misma diferencia que separa un formulario con un botón de “guardar” de un documento que se autoguarda con cada tecla: ambos persisten, pero uno tiene puntos de control y el otro no.

Reactividad de grano fino, no de objeto

Un malentendido frecuente es creer que mutar el proxy notifica a “quien lea el estado”. No: notifica a quien lea la propiedad concreta que tocaste. El rastreo es por clave, igual que en el store. Un efecto que solo lee state.nombre no se despierta cuando cambias state.contador, aunque ambos vivan en el mismo objeto.

TSX
const state = createMutable({ contador: 0, nombre: "Ada" });createEffect(() => console.log(state.nombre));   // solo depende de nombrestate.contador = 99; // el efecto NO corre: no lee contadorstate.nombre = "Grace"; // el efecto corre: lee nombre

Esta granularidad es la que hace atractivo al primitivo: obtienes la ergonomía de mutar un objeto plano y, aun así, cada campo se rastrea por separado sin que reconstruyas nada. Es también la que lo hace peligroso, porque la reactividad se vuelve invisible en el punto de escritura —un tema para la lección 3—. Por ahora, fija el modelo mental exacto: createMutable no es “un store más fácil”, es el mismo store con la puerta de escritura abierta de par en par.

Desestructurar y unwrap: dos formas de perder el proxy

La reactividad vive en el acto de leer una propiedad a través del proxy. Dos operaciones cotidianas rompen ese acto y capturan valores muertos. La primera es desestructurar: const { contador } = state lee state.contador una única vez, guarda el número y corta todo vínculo; a partir de ahí contador es una copia congelada que nunca se refresca. Es la misma regla que gobierna las props, y el mismo error de principiante.

TSX
import { unwrap } from "solid-js/store";const state = createMutable({ contador: 0, config: { zoom: 1 } });// MAL: captura el valor una vez, sin reactividadconst { contador } = state;// BIEN: lee la propiedad en el punto de uso, dentro del ambito reactivocreateEffect(() => console.log(state.contador));// unwrap devuelve el objeto crudo, fuera del proxy: util para interop,// peligroso para escribir, porque mutar lo desenvuelto NO notificaconst crudo = unwrap(state);crudo.contador = 99; // cambia el dato, pero nadie se entera

unwrap es la operación inversa a createMutable: te devuelve el objeto subyacente, sin proxy, para entregárselo a código que no debe rastrear o para comparar estructuras. Pero recuerda que fuera del proxy no hay reactividad: leer del objeto crudo no suscribe y escribir en él no notifica. Trátalo como una ventana de solo lectura hacia el dato, no como un atajo de escritura.