Saltar al contenido

Casos de uso de reconcile: red, sockets, revalidación

reconcile brilla exactamente en la costura donde tu aplicación toca el mundo exterior de los datos remotos: cada respuesta de fetch, cada mensaje de WebSocket, cada revalidación en segundo plano trae referencias frescas con contenido casi igual. Tres escenarios canónicos muestran cómo fusionar esas instantáneas en un store vivo sin remontar listas, sin parpadeos y sin perder el estado del DOM.

Respuestas de fetch: revalidar sin remontar

El caso base es una lista que cargas al montar y vuelves a pedir cada cierto tiempo o al recuperar el foco de la ventana. Sin reconcile, cada revalidación reemplaza el array y remonta todas las filas; con él, solo cambian las que de verdad difieren.

TSX
import { createStore, reconcile } from "solid-js/store";const [state, setState] = createStore({ todos: [] as Todo[] });async function revalidar() {  const frescos = await fetch("/api/todos").then((r) => r.json());  setState("todos", reconcile(frescos, { key: "id" }));}onMount(revalidar);document.addEventListener("visibilitychange", () => {  if (document.visibilityState === "visible") revalidar();});

Lo que ganas es tangible: si el usuario tenía un input a medio escribir en una fila, o la lista scrolleada a cierto punto, o una fila con una animación en curso, todo eso sobrevive a la revalidación porque las filas que no cambiaron conservan su nodo del DOM. La red actualizó los datos sin que el usuario notara ni un parpadeo.

El mismo patrón se aplica a la revalidación disparada por una acción del usuario. Tras un POST que crea o edita un elemento, en vez de insertar a mano en el store puedes volver a pedir la colección y reconciliar: el servidor es la fuente de verdad, y reconcile integra su respuesta tocando solo lo que de verdad cambió respecto a lo que ya mostrabas. Así evitas el clásico bug del estado optimista que diverge de lo que el servidor acabó guardando, porque la instantánea autoritativa se funde con la local sin destruir lo que ambas comparten.

Mensajes de WebSocket: un store vivo

Un socket empuja actualizaciones continuamente, y cada mensaje puede ser una instantánea completa de la colección o un cambio parcial. La decisión clave es qué rama reconciliar según lo que traiga el mensaje.

TSX
const [state, setState] = createStore({ mensajes: [] as Msg[] });const socket = new WebSocket(url);socket.addEventListener("message", (e) => {  const lote = JSON.parse(e.data) as Msg[];  setState("mensajes", reconcile(lote, { key: "id" })); // instantánea completa});onCleanup(() => socket.close());

Cuando el mensaje es la colección entera, reconcilias en la rama de la colección y dejas que el diff descubra qué cambió. Pero cuando el mensaje trae un solo elemento actualizado, reconciliar todo el array es desperdicio: escribes directamente por ruta en ese índice, que ya es una actualización de grano fino sin necesidad de diffear nada.

TSX
// El socket manda un único elemento actualizadosocket.addEventListener("message", (e) => {  const uno = JSON.parse(e.data) as Msg;  const i = state.mensajes.findIndex((m) => m.id === uno.id);  if (i >= 0) setState("mensajes", i, reconcile(uno)); // solo esa fila  else setState("mensajes", (arr) => [...arr, uno]);   // alta});

A alta frecuencia de mensajes, esta distinción deja de ser cosmética. Un socket que emite decenas de frames por segundo y reconcilia toda la colección en cada uno malgasta recorridos completos del árbol para tocar una hoja; dirigir cada frame a su rama exacta mantiene el trabajo proporcional al cambio real y no al tamaño de la colección.

Revalidación en segundo plano

El patrón stale-while-revalidate muestra datos cacheados de inmediato y lanza la petición fresca en paralelo, fusionando el resultado cuando llega. reconcile es lo que hace que esa fusión sea invisible: no hay estado de carga que borre la lista ni remonte que la haga parpadear, solo las filas que cambiaron se actualizan cuando la respuesta aterriza.

Sin reconcile, este patrón se traiciona a sí mismo: mostrarías la caché al instante para, un segundo después, reemplazarla con la respuesta fresca y provocar justo el parpadeo que querías evitar. La caché y la respuesta casi siempre coinciden en la mayoría de sus filas, así que el diff descubre que hay poquísimo que tocar y la transición de “datos viejos” a “datos frescos” ocurre sin que se note un corte.

Qué rama reconciliar

La destreza operativa se reduce a ajustar el ámbito. Reconcilia siempre la rama más estrecha que corresponda al dato que llegó: la colección si vino la colección, el índice si vino un elemento, un subobjeto si vino un fragmento. Cuanto más ajustado el ámbito, menos árbol recorre el diff y menos oportunidades hay de tocar algo que no debía tocarse. Reconciliar la raíz entera por comodidad funciona, pero desperdicia trabajo y, a suficiente frecuencia de mensajes, se nota.

TSX
setState(reconcile(instantaneaGlobal));              // raíz: un snapshot completosetState("todos", reconcile(lista, { key: "id" }));  // rama: la colección que llegósetState("todos", i, reconcile(uno));                // hoja: un elemento por su índice

En la práctica, la mayoría de las integraciones viven en el nivel de rama: el endpoint o el canal corresponde a una colección concreta, y reconcilias esa colección. Reservar la reconciliación de raíz para el arranque o para un snapshot autoritativo completo, y la escritura por índice para los deltas de un elemento, te da un mapa mental claro de a qué altura del store entra cada tipo de mensaje.