Grano fino en stores: solo se actualiza quien lee lo que cambió
El suscriptor de un store no es el componente sino la expresión concreta del DOM que lee la hoja. Por eso cambiar estado.usuario.nombre no re-renderiza ningún componente: reejecuta únicamente el binding ligado a esa hoja y toca solo ese nodo del DOM. Diez componentes que leen diez campos distintos se actualizan de forma independiente, y la topología del árbol de componentes es irrelevante para lo que se repinta.
Quién lee es quien se actualiza
Cuando el compilador de Solid encuentra una lectura dinámica en el JSX —{estado.usuario.nombre}— la envuelve en un pequeño cómputo cuyo único trabajo es mantener actualizado ese nodo del DOM. Ese cómputo es el que lee la hoja, y por tanto es el que se suscribe a ella. La granularidad de la suscripción no es el componente ni la función: es el binding, la expresión concreta que produce un pedazo de DOM.
function Panel() { return ( <> <h1>{estado.usuario.nombre}</h1> {/* binding suscrito a usuario.nombre */} <p>{estado.usuario.edad}</p> {/* binding suscrito a usuario.edad */} </> );}setEstado("usuario", "edad", 37); // solo el <p> se reevalúa; el <h1> ni se tocaCada expresión dinámica tiene su propio cómputo, su propia lista de dependencias y su propio nodo del DOM que actualizar. Por eso decir el componente se actualiza es, en Solid, una imprecisión: el componente no se actualiza jamás. Lo que se actualiza es una expresión, y solo si la hoja que lee ha cambiado.
El giro mental que esto exige es dejar de preguntar qué componente debe actualizarse y empezar a preguntar qué expresiones leen esta hoja. Cambia el sujeto de la frase: no es el componente el que reacciona, son sus lecturas individuales. Un mismo componente puede alojar una expresión que se reejecuta mil veces por segundo junto a otra que no se toca en toda la sesión, y ambas conviven sin interferir, porque cada una es su propio suscriptor con su propia lista de dependencias.
No se re-renderiza el componente, se reejecuta la expresión
El cuerpo del componente corre una sola vez, al crearse. No hay ciclo de render, no hay reconciliación de árboles, no hay comparación de props. Lo único que vuelve a ejecutarse tras un cambio son los cómputos que el compilador tejió alrededor de cada expresión dinámica. Un cambio de estado no reinvoca la función del componente; propaga por el grafo hasta los bindings que dependen de la hoja tocada, y ahí se detiene.
El diagrama hace visible la cadena: setEstado marca sucia la hoja edad, esa hoja solo tiene como oyente al binding del <p>, y ese binding solo escribe en un nodo de texto. La rama de nombre —hoja, binding y nodo— permanece intacta porque nada la tocó. No es que Solid decida no actualizarla tras comparar; es que nunca la incluyó en la propagación, porque no dependía del cambio.
Esta es la diferencia de fondo con un modelo de virtual DOM. Allí, un cambio de estado dispara un nuevo render de un subárbol de componentes, y un algoritmo de diffing decide después qué del DOM tocar. En Solid no hay render nuevo ni diffing: el grafo ya sabe, por construcción, qué bindings dependen de qué hojas, y el cambio viaja directo por esas aristas. El trabajo es proporcional a lo que cambió, no al tamaño del componente que lo contiene.
Grano fino a través de componentes anidados
Lo más contraintuitivo para quien llega de React es que la frontera de un componente no crea ninguna barrera de actualización. Puedes partir la interfaz en tantos componentes como quieras; cada uno que lea una hoja del store se suscribe a ella por su cuenta, y cambiar esa hoja actualiza solo al que la lee, sin cascada por el árbol.
function Nombre() { return <span>{estado.usuario.nombre}</span>;}function Edad() { return <span>{estado.usuario.edad}</span>;}// Ambos leen del mismo store, pero hojas distintas:setEstado("usuario", "edad", 37); // actualiza solo <Edad/>, no <Nombre/>Puedes volverlo tangible instrumentando cada lectura con un contador. Al cambiar una hoja verás incrementarse solo el contador del cómputo que la lee, mientras el otro permanece congelado —la prueba empírica de que la suscripción es por hoja y no por componente:
import { createEffect } from "solid-js";createEffect(() => { estado.usuario.nombre; console.count("lee nombre"); });createEffect(() => { estado.usuario.edad; console.count("lee edad"); });setEstado("usuario", "edad", 40); // incrementa solo el contador "lee edad"Como la suscripción es por hoja, la topología de componentes es irrelevante para lo que se repinta. Da igual que Nombre y Edad sean hermanos, primos o estén a diez niveles de anidamiento uno del otro: lo único que determina qué se actualiza es qué hoja lee cada quién. No hay que memoizar componentes, ni cortar cascadas con memo, ni preocuparse por que un padre repinte a sus hijos, porque nada de eso ocurre.
Por qué desaparecen memo y las listas de dependencias
En un modelo de renders, buena parte del código de optimización existe para contener el trabajo que el propio modelo genera: memoizar componentes para que no se repinten cuando su padre lo hace, envolver callbacks para que no cambien de identidad, declarar listas de dependencias para acotar cuándo corre un efecto. En Solid con un store, ese código no se optimiza: sobra, porque el problema que resolvía no existe.
// No hay padre que repinte al hijo: no hay nada que memoizarfunction Lista() { return <For each={estado.filas}>{(fila) => <Fila dato={fila} />}</For>;}// Cambiar fila.n actualiza solo el binding de esa fila; ni Lista ni las demás Fila correnEl tracking automático sustituye a las listas de dependencias: un binding depende exactamente de las hojas que leyó en su última ejecución, ni una más ni una menos, y esa lista se recalcula sola en cada corrida. No hay un array de dependencias que mantener sincronizado a mano, ni el error clásico de olvidar una y quedarse con un valor rancio. El grafo es la lista de dependencias, y siempre está al día.