La potencia de createMutable: imperativo, getters y setters, interop
Donde createMutable brilla: código imperativo que se lee como JavaScript de toda la vida —state.items.push, state.n++, delete state.x—, getters que se vuelven derivaciones reactivas y setters que interceptan la asignación como en una clase, e interoperabilidad con APIs de terceros que esperan mutar un objeto plano. Por qué esa naturalidad es real y no un truco, y qué patrones desbloquea.
Imperativo de verdad, no imitado
En un signal o un store, la mutación en sitio es un pecado: arr.push(x) no notifica porque conserva la referencia. En un createMutable, ese pecado se convierte en la vía correcta. El proxy intercepta las operaciones de array, el incremento, el borrado de claves, y traduce cada una en la notificación granular adecuada.
import { createMutable } from "solid-js/store";const state = createMutable({ items: [] as string[], contador: 0, config: { tema: "oscuro" } as { tema: string; zoom?: number },});state.items.push("a"); // reactivo: notifica a quien lea itemsstate.contador++; // reactivo: lee y reasigna en una expresionstate.config.zoom = 1.5; // reactivo: anade clave nuevadelete state.config.tema; // reactivo: borra clave existenteNada de esto requiere pensar en referencias, spread ni actualizadores funcionales. El código dice lo que hace y hace lo que dice. Para lógica intrínsecamente imperativa —una máquina de estados de juego, un acumulador que crece en un bucle, una estructura que se edita paso a paso— esta transparencia elimina toda la fricción entre la intención y la sintaxis. Escribes el algoritmo, no su codificación inmutable.
Getters como derivaciones, setters como intercepción
Como todo store, createMutable respeta los descriptores de propiedad del objeto inicial. Un getter deja de ser un campo y se vuelve una derivación reactiva: se recalcula cuando cambian las propiedades que lee, y sus lectores se suscriben a él como a cualquier fuente. Un setter te da un punto de intercepción para validar, normalizar o descomponer una asignación.
const usuario = createMutable({ nombre: "Grace", apellido: "Hopper", get completo() { return `${this.nombre} ${this.apellido}`; }, set completo(valor: string) { const [n, a] = valor.split(" "); this.nombre = n; this.apellido = a; },});usuario.completo; // "Grace Hopper" (derivado)usuario.completo = "Ada Lovelace"; // el setter descompone: nombre y apellido cambianusuario.completo; // "Ada Lovelace"El this dentro del getter y del setter apunta al propio proxy, de modo que leer this.nombre teje la dependencia y asignar this.nombre propaga el cambio. Esto acerca createMutable al modelo mental de las clases con propiedades computadas: encapsulas la lógica derivada junto a los datos, en el mismo objeto, en lugar de repartirla entre signals y memos externos. Para quien viene de un mundo orientado a objetos, es el estado reactivo que menos traducción mental exige.
Dos matices para no idealizar los getters. Primero, no se memoizan solos: cada lectura vuelve a ejecutar el cuerpo del getter, así que una derivación cara —un filtrado, un ordenamiento— conviene respaldarla con un createMemo externo en lugar de dejarla recomputar en cada acceso. Segundo, el this solo apunta al proxy si el getter, el setter o el método son funciones normales; si los defines como funciones flecha, this no se liga al objeto y la reactividad no se teje. La sintaxis abreviada de método —play() { ... }— y el get/set clásicos son la forma segura.
Interoperar con código que solo sabe mutar
El tercer punto fuerte es el menos glamuroso y el más decisivo en la práctica: hay APIs y librerías enteras cuyo contrato es escribir sobre un objeto que tú les das. Un motor de física que actualiza cuerpo.x cada frame, una librería de formularios que asigna form.campo = valor, un binding de terceros que espera un objeto plano y lo muta sin preguntar. Con un store, ese código rompería la reactividad, porque mutar el proxy por fuera de setStore no se rastrea. Con createMutable, la mutación externa es la vía reactiva: la librería escribe como siempre y Solid se entera igual.
const fisica = createMutable({ x: 0, y: 0, vx: 2, vy: 1 });// Un bucle externo que ni conoce Solid muta el objeto en cada framefunction paso(cuerpo: { x: number; y: number; vx: number; vy: number }) { cuerpo.x += cuerpo.vx; cuerpo.y += cuerpo.vy;}// La vista que lee fisica.x y fisica.y se actualiza solarequestAnimationFrame(function bucle() { paso(fisica); // mutacion externa, agnostica de Solid requestAnimationFrame(bucle);});La clave conceptual es que el proxy es transparente al patrón de escritura: no exige que quien muta conozca Solid, ni que pase por una API especial. Cualquier código que trate al objeto como mutable —tuyo o de un tercero— queda automáticamente conectado al grafo. Esa es la razón por la que createMutable sobrevive como herramienta de interoperabilidad aunque se desaconseje para el estado de aplicación ordinario: es el único puente que no pide permiso a la librería del otro lado.