wandres.dev
CREATEMUTABLE · el store mutable

modifyMutable con produce y reconcile: lotes controlados

Varias asignaciones directas sobre un mutable son varias propagaciones; modifyMutable las funde en una sola aplicando un modificador dentro de un batch. produce te da un borrador que mutas imperativamente y notifica una vez; reconcile difunde un valor nuevo contra el estado actual y aplica solo los cambios mínimos, preservando identidad. La pieza que devuelve control y coherencia a la escritura libre de createMutable.

⏱ 18 min

La escritura libre de createMutable tiene un coste que la comodidad esconde: cada asignación suelta es una propagación independiente. Cambiar tres campos seguidos fuera de un ciclo reactivo despierta a los consumidores tres veces, exponiéndolos a estados intermedios que nunca fueron reales. modifyMutable es la herramienta que devuelve control a esa escritura: aplica un modificador —produce o reconcile— dentro de un único lote, fundiendo muchas mutaciones en una sola propagación coherente. Es el primitivo que reconcilia la ergonomía de mutar con la disciplina de notificar una vez.

🎯 Al terminar esta lección sabrás
  • Ver por qué varias asignaciones directas sobre un mutable disparan varias propagaciones.
  • Usar modifyMutable para agrupar mutaciones en un único lote coherente.
  • Aplicar produce para escribir un borrador imperativo que notifica una sola vez.
  • Aplicar reconcile para difundir un valor nuevo con cambios mínimos e identidad preservada.

El problema: muchas escrituras, muchas pasadas

Fuera de una computación, cada asignación a un mutable abre y cierra su propia propagación. Si actualizas tres campos relacionados en sucesión, los memos y efectos que dependen de ellos corren tres veces, y en dos de esas veces observan un estado a medio construir. No es incorrección —el resultado final es bueno— sino trabajo redundante y coherencia rota para los observadores.

import { createMutable } from "solid-js/store";

const state = createMutable({ nombre: "Ada", apellido: "Byron", edad: 36 });

// Tres asignaciones sueltas -> tres propagaciones
state.nombre = "Grace";    // pasada 1
state.apellido = "Hopper"; // pasada 2 (aqui alguien ve "Grace Byron")
state.edad = 85;           // pasada 3

Ese "Grace Byron" transitorio es el síntoma. Conceptualmente las tres líneas son un solo cambio —“pasamos de una persona a otra”— pero el sistema las ve como tres eventos porque nada le dice que forman una unidad.

modifyMutable: un lote alrededor del modificador

modifyMutable(mutable, modificador) recibe el proxy y una función modificadora, y aplica sus cambios dentro de un único batch. Los consumidores se recalculan una sola vez, con el estado ya completo. Su firma —modifyMutable<T>(mutable: T, modifier: (state: T) => T): void— revela que el segundo argumento es justo lo que devuelven produce y reconcile: modificadores que transforman el estado.

import { modifyMutable, produce } from "solid-js/store";

// Una sola propagacion: nadie ve el estado intermedio
modifyMutable(state, produce((s) => {
  s.nombre = "Grace";
  s.apellido = "Hopper";
  s.edad = 85;
}));

Es la misma relación que batch tiene con los signals, adaptada al mundo del store: declaras que un conjunto de escrituras es semánticamente una, y el sistema recorre el grafo una vez en lugar de tantas como mutaciones hiciste.

flowchart TD
subgraph Sueltas
  A1[nombre = Grace] --> P1[pasada 1]
  A2[apellido = Hopper] --> P2[pasada 2 estado intermedio]
  A3[edad = 85] --> P3[pasada 3]
end
subgraph Con modifyMutable
  B[produce muta los tres campos] --> L[un solo batch] --> P[una sola pasada]
end
style P2 fill:#f38ba8,color:#11111b
style P fill:#a6e3a1,color:#11111b
style L fill:#89b4fa,color:#11111b

produce: escribir el cambio como un borrador

produce(fn) devuelve un modificador que te entrega un borrador del estado —un proxy vivo solo dentro de la función— sobre el que escribes con sintaxis imperativa plena. Cada mutación que hagas dentro se traduce en una notificación granular, pero todas se emiten juntas al cerrar el lote. Es el patrón para cambios que conoces campo a campo: sabes exactamente qué tocar y quieres tocarlo mutando.

import { modifyMutable, produce } from "solid-js/store";

const estado = createMutable({ items: [] as { id: number; hecho: boolean }[] });

modifyMutable(estado, produce((s) => {
  s.items.push({ id: 1, hecho: false });
  s.items.push({ id: 2, hecho: false });
  s.items[0].hecho = true;   // mutas hondo, con naturalidad
}));                          // todo propaga una vez

La virtud de produce es que no renuncias a nada de la ergonomía imperativa —push, asignación por índice, mutación anidada— y a cambio obtienes el control del lote. Es, en esencia, la misma herramienta que usarías con setStore en un createStore; modifyMutable es el puente que te deja aplicarla sobre un mutable.

reconcile: difundir un valor nuevo con cambios mínimos

reconcile(valor) resuelve un problema distinto. En lugar de que tú describas los cambios, le das el estado final completo y él calcula la diferencia contra el actual, aplicando solo las mutaciones mínimas necesarias. Su ganancia es la preservación de identidad: las partes que no cambiaron conservan su referencia, así que no se disparan sus lectores ni se recrean sus nodos del DOM. Es la herramienta canónica para reemplazar datos que llegan enteros de fuera —una respuesta de red— sin tirar y reconstruir todo el árbol.

import { modifyMutable, reconcile } from "solid-js/store";

const estado = createMutable({ usuarios: [] as { id: number; nombre: string }[] });

async function refrescar() {
  const frescos = await fetch("/api/usuarios").then((r) => r.json());

  // Difunde el array nuevo contra el actual: solo cambian las filas distintas
  modifyMutable(estado, reconcile(frescos, { key: "id" }));
}

La opción key le dice a reconcile cómo emparejar elementos entre el estado viejo y el nuevo: con key: "id", una fila con el mismo id se considera la misma entidad y se actualiza en sitio en vez de recrearse. El resultado es que una lista que se refresca cada pocos segundos solo repinta las filas que de verdad cambiaron, preservando foco, scroll y estado local de las que no. Es la diferencia entre parpadear la tabla entera y editar quirúrgicamente sus celdas.

✍️

produce: tú describes el cambio

Un borrador mutable donde escribes campo a campo con sintaxis imperativa. Ideal cuando sabes exactamente qué tocar. Notifica una vez al cerrar el lote.

🔀

reconcile: tú das el resultado

Le entregas el valor final y él difunde la diferencia, aplicando cambios mínimos y preservando identidad. Ideal para datos que llegan enteros de fuera.

ℹ️
La misma dualidad existe en createStore

produce y reconcile no son exclusivos del mutable: son modificadores de solid-js/store que también pasas a setStore. La distinción conceptual se mantiene en ambos mundos —produce cuando conoces los cambios, reconcile cuando tienes el valor final—. Lo único que aporta modifyMutable es la vía para aplicarlos sobre un createMutable, que al no tener un setStore propio necesitaba una función que abriera el lote por él.

⚠️
Sin modifyMutable, produce y reconcile no tienen dónde vivir en un mutable

Un error de principiante es intentar mutar el proxy y “envolverlo en produce” a mano, o llamar a reconcile como si fuera una función que muta. No lo es: produce y reconcile devuelven modificadores, no ejecutan nada por sí solos. Necesitan un aplicador que les pase el estado y abra el lote. En createStore ese aplicador es setStore; en createMutable es modifyMutable. Sin él, tienes un modificador colgando sin efecto.

modifyMutable es donde createMutable pide perdón por su propia libertad

Hay una ironía elegante en este último primitivo del nivel. createMutable existe para liberarte de la ceremonia de la escritura canalizada: mutas donde quieras, como quieras, y funciona. Pero esa misma libertad reintroduce, por la puerta de atrás, dos de los problemas que la escritura canalizada resolvía —la propagación múltiple cuando varias escrituras son un solo cambio, y la reconstrucción total cuando llega un valor nuevo que en su mayoría es idéntico al viejo—. modifyMutable con produce y reconcile es la forma en que el mutable recupera, voluntariamente y solo cuando lo pides, la disciplina que había tirado por la ventana. Y observa la simetría perfecta que cierra el nivel entero: produce es la disciplina de la coherencia —declarar que muchas escrituras son un hecho, para que el sistema las vea como uno—; reconcile es la disciplina de la identidad —declarar que un valor nuevo es en su mayoría el viejo, para que el sistema conserve lo que no cambió—. Ambas son exactamente los contratos que createStore te da de serie y que createMutable te quita a cambio de ergonomía. Que la solución sea volver a pedirlos a mano, escritura por escritura, dice todo lo que hay que saber sobre este primitivo: es una herramienta que te da libertad total y luego te vende de vuelta, en piezas, la estructura que necesitas para no hacerte daño con ella. Usada con maestría —el proxy encapsulado tras una fachada, las escrituras múltiples agrupadas con produce, los datos externos difundidos con reconcilecreateMutable deja de ser el primitivo peligroso que el equipo desaconseja y se convierte en lo que siempre fue capaz de ser: un traductor de frontera, imperativo por fuera y coherente por dentro. Y el hecho de que hagan falta tres primitivos más para domarlo es, en sí mismo, la mejor explicación de por qué createStore sigue siendo el camino por defecto.

⚔️ Controla el lote y la identidad
  1. Muta tres campos de un mutable en líneas sueltas con un efecto que los lea; cuenta las propagaciones y localiza el estado intermedio.
  2. Agrupa esas tres mutaciones con modifyMutable y produce, y confirma que la propagación baja a una.
  3. Modela una lista, píntala con For, y refréscala con un array nuevo usando reconcile con key; observa que solo cambian las filas distintas.
  4. Repite el refresco reemplazando el array a mano y compara cuántos nodos del DOM se recrean en un caso y en el otro.
  5. Explica cuándo elegirías produce sobre reconcile y viceversa, en términos de si conoces los cambios o el resultado.