wandres.dev
REACTIVIDAD PROFUNDA · Proxies y el estado anidado

Atómico frente a profundo: dos decisiones distintas

Señales independientes o un objeto instrumentado no son dos formas de lo mismo: difieren en granularidad por defecto, en atomicidad, en depurabilidad y en cómo escalan con el tamaño del modelo.

⏱ 17 min

Cerramos el nivel comparando los dos modelos de estado que conviven en esta familia de motores. No son alternativas de sintaxis: difieren en dónde se decide la granularidad, en quién garantiza la atomicidad, en qué se puede ver leyendo el código y en cómo crece el coste con el modelo. Casi todos los motores maduros acaban ofreciendo los dos, y saber cuándo usar cada uno es una decisión de diseño real.

🎯 Al terminar esta lección sabrás
  • Comparar los dos modelos en las cinco dimensiones que los diferencian.
  • Determinar quién decide la granularidad en cada uno.
  • Reconocer los patrones donde cada modelo es claramente superior.
  • Combinar ambos en la misma aplicación sin incoherencias.

Las cinco dimensiones

dimensión señales atómicas estado profundo
granularidad la decide el programador al repartir automática, por propiedad
atomicidad hay que agrupar con lotes igual, hay que agrupar
coste de lectura una llamada a función una trampa más trabajo
coste del modelo proporcional a las señales creadas proporcional a lo leído
visibilidad explícita en la sintaxis invisible en la sintaxis

La primera fila es la decisiva y merece detenerse. Con señales atómicas, tú decides qué es una unidad de cambio. Si creas una señal para un objeto entero, el grano es el objeto; si creas una por campo, el grano es el campo. Con estado profundo, el grano es siempre la propiedad, sin que nadie decida.

Cuándo gana cada uno

Cuando el atómico es claramente mejor

Estado plano y pequeño. Un formulario con ocho campos, un panel con tres interruptores. Crear ocho señales es trivial, el grano es óptimo, y no se paga ninguna indirección.

Estado que se reemplaza entero. Una respuesta de red que llega y sustituye a la anterior. No hay mutación por dentro, así que el grano por propiedad no aporta nada y la instrumentación es puro coste.

Cuando el rendimiento de lectura manda. Bucles, cálculos, cualquier cosa que acceda mucho. Ya lo vimos en la lección 3.

Cuando quieres ver la reactividad en el código. Una llamada con paréntesis es visible; un acceso a propiedad no lo es. Para revisar código y para depurar, la explicitud vale mucho.

Cuando el profundo es claramente mejor

Estado anidado profundo. Un documento, un árbol, una configuración con secciones. Repartir eso en señales exigiría una señal por nodo y un mecanismo para navegar entre ellas, es decir, reimplementar el proxy peor.

Estado con forma dinámica. Si las claves no se conocen de antemano —un diccionario de entidades por identificador, un formulario generado— no puedes crear las señales por adelantado. Un proxy las crea al leer.

Mutaciones parciales frecuentes. Cambiar un campo de un elemento de una lista de mil. Con una señal para la lista, cualquier cambio ensucia a todos los lectores; con un proxy, solo a quien lea ese campo.

Cuando el equipo no va a acertar con el grano. Ya lo dijimos en la lección 3 y es el argumento más honesto: un proxy da granularidad óptima sin pensar, y un reparto manual mal hecho es peor que cualquier proxy.

flowchart TB
D{como es tu estado} --> P[plano y pequeno]
D --> A[anidado y profundo]
D --> R[se reemplaza entero]
D --> M[se muta por partes]
P --> S1[senales atomicas]
R --> S1
A --> S2[estado profundo]
M --> S2
style D fill:#f9e2af,color:#11111b
style P fill:#89b4fa,color:#11111b
style A fill:#cba6f7,color:#11111b
style R fill:#89b4fa,color:#11111b
style M fill:#cba6f7,color:#11111b
style S1 fill:#a6e3a1,color:#11111b
style S2 fill:#a6e3a1,color:#11111b

Qué ofrece cada motor

Solid ofrece los dos con una separación nítida: createSignal para lo atómico y createStore para lo profundo, con proxies de solo lectura y una función de actualización explícita, más utilidades como produce para escribir con sintaxis mutable y reconcile para fusionar datos nuevos conservando la identidad de lo que no cambió.

Vue ofrece ref para lo atómico y reactive para lo profundo, más shallowRef y shallowReactive para los casos intermedios. La documentación recomienda ref como opción por defecto, en buena medida por la trampa de la desestructuración de la lección anterior.

Svelte 5 unifica ambos bajo $state: si el valor es un objeto o un array, se envuelve en un proxy; si es un primitivo, es atómico. $state.raw desactiva la envoltura para el caso en que el valor se reemplace entero.

Angular y Preact Signals son solo atómicos. En Angular, el estado profundo se modela con señales anidadas o con estructuras inmutables que se reemplazan; hay librerías del ecosistema que añaden almacenes profundos encima.

Combinarlos, y cierre del nivel

Combinar los dos sin incoherencias

La mezcla es normal y funciona, con tres reglas.

Una frontera clara por módulo. Decide para cada área de estado cuál de los dos modelos usa y no los mezcles dentro de la misma estructura. Un objeto profundo con señales dentro funciona pero es difícil de razonar.

No guardes señales dentro de objetos profundos. El proxy envolvería la señal y acabarías con dos capas de reactividad sobre el mismo dato, con propagaciones duplicadas.

Convierte en la frontera. Si un módulo atómico necesita pasar datos a uno profundo, convierte explícitamente en el punto de paso, con el objeto crudo, y documenta la dirección del flujo.

La eleccion no es de sintaxis, es de quien decide el grano

La conclusión del nivel, y la que hay que llevarse: elegir entre atómico y profundo es elegir quién decide la granularidad del grafo. Con señales atómicas la decides tú, en cada línea donde creas una, y esa decisión es la que fija el coste de tu sistema según vimos en el nivel 1. Con estado profundo la decide el motor, siempre igual, siempre por propiedad. Eso significa que el modelo atómico tiene más techo y más suelo: bien usado, produce el grafo óptimo con el mínimo coste de indirección; mal usado, produce un grafo de grano grueso con toda la maquinaria fina encima. El estado profundo tiene un techo algo más bajo, por la indirección, y un suelo mucho más alto, porque es imposible equivocarse con el grano. Visto así, la pregunta correcta al elegir no es cuál es mejor sino cuánta varianza puedes permitirte. En un proyecto pequeño con gente que entiende el motor, el atómico gana. En un proyecto grande con rotación de gente y un modelo de datos que evoluciona, el suelo alto del profundo vale más que el techo del atómico, porque el rendimiento medio de una base de código lo determinan sus peores partes y no las mejores. Es exactamente el mismo razonamiento que lleva a preferir un recolector de basura sobre la gestión manual de memoria en la mayoría de los proyectos, y merece la pena reconocerlo como lo que es: una decisión sobre el proceso de desarrollo tanto como sobre la tecnología.

Cierre del nivel

Un proxy no añade reactividad: la hace invisible, traduciendo accesos a propiedad en operaciones sobre las mismas fuentes y aristas de siempre. Su instrumentación perezosa hace que el coste sea proporcional al uso y no al tamaño del modelo. Paga una indirección en cada acceso y hace que la reactividad deje de verse en el código, con la trampa de la desestructuración como consecuencia más visible.

Con esto quedan cubiertas las ocho decisiones de diseño que enumeramos en el nivel 0. Lo que viene es ver qué eligió cada motor real en cada una de ellas.

⚔️ Modela lo mismo de las dos formas
  1. Coge una estructura de estado real de tu aplicación y modélala con señales atómicas y con estado profundo.
  2. Cuenta las líneas de cada versión y los nodos reactivos que crea cada una.
  3. Mide los cuerpos ejecutados al cambiar un campo profundo en ambas.
  4. Identifica en qué punto de tu versión atómica el grano quedó más grueso de lo ideal, y estima cuánto trabajo extra cuesta.