La reactividad de grano fino
Qué significa exactamente que solo se actualice el nodo que depende de un signal: el átomo reactivo, el grafo de sources y observers, y por qué Solid no re-ejecuta tu componente.
El átomo reactivo: un signal es una suscripción
Un signal es el primitivo fundamental. createSignal devuelve una pareja: un getter (una función) y un setter. Leer el getter no es “leer una variable”: es declarar una suscripción en el contexto reactivo donde lo lees.
import { createSignal, createEffect } from "solid-js";function Contador() { const [count, setCount] = createSignal(0); const [tema, setTema] = createSignal("azul"); // Este efecto lee count(): se suscribe SOLO a count. createEffect(() => console.log("count =", count())); return ( <button class={tema()} onClick={() => setCount(count() + 1)}> Clics: {count()} </button> );}El cuerpo de Contador corre una sola vez. Cuando llamas a setCount, Solid no vuelve a invocar la función del componente: consulta quién leyó count() y re-ejecuta solo a esos suscriptores. Aquí son dos: el createEffect del console.log y la expresión {count()} del JSX, que por dentro es su propio efecto ligado a un único nodo de texto. La class que lee tema() no se toca, porque nadie que dependa de tema cambió.
Grano fino: reacciona el nodo, no el componente
Esta es la diferencia que hay que interiorizar. En un modelo de re-render, cambiar un estado re-ejecuta el componente completo y produce una descripción nueva de toda su UI. En Solid, cambiar un signal dispara solo las computaciones suscritas, y cada una hace la operación mínima sobre el DOM real.
La granularidad es la del binding, no la del componente ni la de un <div> entero. Si un componente pinta cien campos y cambia uno, en Solid se actualiza exactamente ese campo: no hay una función que reconstruya los cien para que un diff descubra cuál difiere. El descubrimiento ya ocurrió cuando leíste el getter.
Conviene subrayar qué NO ocurre en este proceso. No se construye una representación intermedia de la interfaz. No se compara nada contra un estado anterior. No se marca el componente como “sucio” para revisarlo en un ciclo posterior. El setter localiza la lista de suscriptores del signal y ejecuta exactamente esos; el resto del programa ni se entera de que algo cambió.
Esa ausencia de trabajo especulativo —hacer cosas “por si acaso” para luego descartarlas— es la esencia del grano fino, y es lo que separa a Solid de cualquier sistema que primero produce una descripción completa de la UI y después decide qué parte de ella aplicar.
El grafo: sources, observers y propagación
Por debajo, Solid mantiene un grafo dirigido. Los sources son lo que se puede leer (signals y memos). Los observers son lo que reacciona (efectos y memos). Cuando un observer lee un source durante su ejecución, Solid graba la arista en ambos sentidos: el source sabe quién lo observa y el observer sabe de qué depende.
Las aristas son dinámicas: se reconstruyen en cada ejecución del observer. Si un efecto solo lee b() cuando a() es verdadero, deja de depender de b en cuanto a se vuelve falso. No hay array de dependencias que mantener a mano: el grafo se dibuja solo a partir de las lecturas reales.
import { createSignal, createEffect } from "solid-js";const [detallado, setDetallado] = createSignal(true);const [valor, setValor] = createSignal(10);// Cuando detallado() es false, el efecto deja de depender de valor().createEffect(() => { if (detallado()) console.log("detalle:", valor()); else console.log("resumen");});Un createMemo ocupa una posición doble en el grafo: es observer de sus fuentes y, a la vez, source para quien lo lee. Encadenando signal a memo a efecto construyes derivaciones cacheadas, y Solid solo recalcula un memo cuando de verdad cambia alguna de sus fuentes. Ese encadenamiento es lo que deja componer lógica reactiva compleja sin recalcular de más ni ensuciar el DOM con pasos intermedios.
Por qué esto cambia el modelo mental
Si vienes de un modelo de re-render, tu instinto es preguntar “¿cuándo se vuelve a renderizar esto?”. En Solid la pregunta correcta es “¿qué lee este valor?”. El estado no dispara renders: dispara actualizaciones puntuales de los nodos que lo leyeron. Los memoizadores manuales, la comparación de props y las claves para evitar trabajo repetido dejan de ser necesarios, porque nunca hubo trabajo repetido que evitar.
Esa economía tiene una consecuencia práctica enorme: el rendimiento deja de ser algo que optimizas después y pasa a ser el comportamiento por defecto. No existe una versión lenta que haya que arreglar con memo; la primera versión ya hace el trabajo mínimo. Tu esfuerzo se traslada de “evitar renders” a “modelar bien las dependencias entre datos”, que es donde de verdad vive la complejidad de una interfaz.
Este es, en el fondo, el argumento entero de Solid en una sola frase: si el sistema sabe con exactitud qué depende de qué, no necesita adivinar, y todo lo que otros frameworks construyen para adivinar mejor —heurísticas de diffing, memoización, claves de lista— se vuelve innecesario. La reactividad de grano fino no es una técnica de optimización que aplicas encima: es la decisión de no generar, para empezar, el trabajo que después habría que optimizar.