Comparación honesta con React
Modelo mental, coste de runtime, hooks frente a primitivos y closures obsoletas: en qué se diferencian Solid y React de verdad, y cuándo cada uno sigue siendo la elección correcta.
Solid y React comparten el JSX y poco más. Debajo hay dos filosofías opuestas: React re-ejecuta tu componente en cada cambio y reconcilia el resultado; Solid ejecuta el componente una vez y deja un grafo reactivo vivo. Esta lección compara ambos sin fanatismo: dónde gana Solid, qué precio paga, y por qué React sigue siendo, para mucha gente, la respuesta correcta.
- Contrastar los dos modelos mentales: re-render frente a grafo persistente.
- Comparar hooks y primitivos: reglas, dependencias y closures.
- Situar el coste de runtime de cada enfoque con honestidad.
- Saber cuándo React es la mejor opción pese a todo.
Dos modelos mentales
En React, el componente es una función que se vuelve a ejecutar cada vez que cambia su estado o sus props. Cada render produce valores nuevos, closures nuevas y un árbol virtual nuevo que se reconcilia. El estado sobrevive entre renders gracias a los hooks, pero el cuerpo de la función es efímero y se repite.
En Solid, el componente es una función que corre una vez para cablear el grafo reactivo. A partir de ahí no se vuelve a ejecutar: lo que reacciona son los efectos y las expresiones del JSX. El cuerpo es una fase de montaje, no un bucle de render.
De esa única diferencia se sigue casi todo lo demás. Si el componente se re-ejecuta, cada objeto y cada función que declaras dentro son nuevos en cada render, y por eso React necesita useCallback, useMemo y arrays de dependencias para estabilizar identidades. Si el componente corre una vez, esas identidades son estables por construcción y ese andamiaje entero sobra. No es que Solid tenga mejores herramientas para el mismo problema: es que no tiene el problema.
flowchart TD R[React: cambia estado] --> R1[Re-ejecuta el componente] R1 --> R2[Nuevas closures y VDOM] R2 --> R3[Reconcilia y parchea] S[Solid: cambia signal] --> S1[Ejecuta solo los observers] S1 --> S2[Actualiza el nodo exacto] style R fill:#f38ba8,color:#11111b style R1 fill:#fab387,color:#11111b style S fill:#89b4fa,color:#11111b style S2 fill:#a6e3a1,color:#11111b
Ninguno de los dos modelos es “más simple” en abstracto; son simples en dimensiones distintas. React tiene una sola regla —todo se re-ejecuta— y una larga lista de excepciones para domarla. Solid tiene varias primitivas —signal, memo, effect— y casi ninguna excepción, porque cada una hace una cosa y el grafo se ocupa del resto. Elegir es, en parte, decidir qué tipo de simplicidad encaja mejor con tu forma de pensar.
Hooks frente a primitivos
Los hooks de React y los primitivos de Solid se parecen en la superficie (useState frente a createSignal, useEffect frente a createEffect), pero obedecen a reglas opuestas porque viven en modelos distintos.
| Aspecto | React (hooks) | Solid (primitivos) |
|---|---|---|
| Cuándo corre el cuerpo | En cada render | Una sola vez |
| Dependencias de efectos | Array manual explícito | Rastreo automático por lectura |
| Orden de llamada | Fijo, no condicional | Libre: en if, bucles o funciones |
| Valor de estado | Nuevo binding por render | Getter estable que siempre lee lo actual |
| Evitar trabajo | memo, useMemo, useCallback |
Innecesario: no hay re-render |
La regla de oro de los hooks —“llámalos siempre en el mismo orden, nunca dentro de condicionales”— existe porque React los identifica por posición en cada render. Los primitivos de Solid no tienen esa restricción: como el cuerpo corre una vez, puedes crear un signal dentro de un if o un createEffect dentro de un bucle sin romper nada.
Esa libertad no es cosmética: permite factorizar lógica reactiva en funciones normales —los llamados custom primitives— sin las reglas especiales de los custom hooks. Un primitivo de Solid es solo una función que crea signals y efectos; se compone como cualquier otra función de JavaScript, sin prefijos mágicos, sin un linter que vigile su uso y sin la restricción de llamarlo en el nivel superior.
La misma divergencia aparece en las derivaciones. En React memoizas para no recalcular en cada render; en Solid, un createMemo se recalcula solo si su fuente cambia y no lleva array de dependencias.
// React: memoizar para evitar recalcular en cada render
const total = useMemo(() => items.reduce((a, b) => a + b.precio, 0), [items]);
// Solid: se recalcula solo si items() cambia, sin declarar dependencias
const total = createMemo(() => items().reduce((a, b) => a + b.precio, 0));
Closures obsoletas y arrays de dependencias
El punto donde la diferencia se vuelve tangible es el manejo de efectos con el tiempo. En React, cada render captura sus propias closures, y un array de dependencias mal puesto congela valores viejos: la temida stale closure.
// React: hay que declarar dependencias, y las closures pueden quedar obsoletas
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount((c) => c + 1), 1000);
return () => clearInterval(id);
}, []); // si aqui leyeras count directo, se congelaria en 0
return <p>{count}</p>;
}
// Solid: el cuerpo corre una vez, count() siempre lee el valor actual
import { createSignal, onCleanup } from "solid-js";
function Timer() {
const [count, setCount] = createSignal(0);
const id = setInterval(() => setCount((c) => c + 1), 1000);
onCleanup(() => clearInterval(id));
return <p>{count()}</p>;
}
En Solid no hay array de dependencias porque el rastreo es automático, y no hay closure obsoleta porque count es un getter estable: llamarlo siempre devuelve el valor vigente. El onCleanup se ata al ciclo de vida del ámbito reactivo, no a un render concreto. Categorías enteras de bugs de React —dependencias omitidas, identidades de función inestables, efectos que se disparan de más— simplemente no tienen dónde ocurrir.
React mitiga estos filos con actualizaciones funcionales (setCount(c => c + 1)), useRef para valores estables, el linter de hooks y, más recientemente, un compilador que inserta memoización automáticamente. Funcionan bien, pero son respuestas a un problema que el modelo de Solid no genera: la diferencia no es tener mejores parches, sino no necesitar ninguno.
Migrar a Solid no es aprender otra sintaxis: es desaprender un reflejo. En React tu modelo es “el componente se re-ejecuta, así que debo controlar qué se recalcula”: de ahí useMemo, useCallback, memo y las listas de dependencias. Ese andamiaje entero es una respuesta al re-render. En Solid el re-render no existe, así que el andamiaje sobra. La pregunta “¿cuándo se vuelve a ejecutar esto?” se sustituye por “¿qué lee este valor?”. Cuando ese cambio cuaja, el código se vuelve más plano y más literal: describes las relaciones entre datos una vez y el grafo las mantiene. La dificultad de Solid no es técnica, es de hábito: cuesta creer que no haga falta optimizar nada porque no hay nada repetido que optimizar.
Coste de runtime, con honestidad
React envía un reconciliador y hace trabajo proporcional al tamaño del árbol que se re-renderiza; lo mitiga muy bien con memoización, y con los Server Components mueve parte del coste al servidor. Solid envía un runtime diminuto y hace trabajo proporcional a lo que cambió, sin memoización manual. En interfaces muy dinámicas —tablas vivas, editores, dashboards en tiempo real— la ventaja de Solid es estructural. En apps donde el render no es el cuello de botella, la diferencia de rendimiento puede ser irrelevante frente a otros factores.
La honestidad pide señalar hacia dónde va React: los Server Components y su nuevo compilador buscan reducir el coste del re-render acercándose, por otra vía, a lo que Solid logra por diseño. Son estrategias distintas para el mismo fin —hacer menos trabajo en el cliente—, y escoger entre ellas es, en el fondo, decidir dónde quieres que viva la complejidad: en un runtime que reconcilia de forma cada vez más lista, o en un modelo que no reconcilia en absoluto.
En cifras redondas, el runtime de Solid ronda unos pocos kilobytes y su trabajo por actualización tiende al mínimo; React parte de un runtime mayor y lo compensa con años de optimización y un ecosistema imbatible. Para una landing o una app pequeña esa diferencia es ruido; para una consola de datos que se refresca sin descanso, es la frontera entre una interfaz fluida y una entrecortada.
El modelo de signals de Solid influyó en una propuesta de TC39 para llevar señales al propio JavaScript, en la que colaboran autores de varios frameworks. La reactividad de grano fino dejó de ser una rareza de un framework para volverse una corriente de fondo del lenguaje.
Que el lenguaje mismo se mueva hacia los signals cambia el marco de la comparación. Durante años, “React o Solid” enfrentaba dos filosofías rivales; a medida que los signals se estandarizan, se parece más a elegir qué framework abraza antes y mejor lo que empieza a ser el modelo por defecto de la plataforma web. Solid no tiene que reconvertirse para llegar ahí: ya está construido así desde el primer día.
Con el coste de runtime sobre la mesa, el veredicto por áreas es matizado y no arroja un ganador único.
React gana en
Ecosistema y contratación masivos, React Native para móvil, madurez de Server Components y una enorme oferta de librerías y talento.
Solid gana en
Rendimiento de grano fino sin esfuerzo, bundle mínimo, ausencia de closures obsoletas y un modelo mental más directo y predecible.
Comparten
JSX, componentes, la cultura de composición y buena parte del vocabulario. Migrar la sintaxis es fácil; migrar el modelo mental es el trabajo real.
Ni Solid hace obsoleto a React ni al revés: resuelven el mismo problema con contratos distintos. La madurez consiste en conocer los dos modelos lo bastante bien como para elegir por restricciones —equipo, plataforma, tipo de interfaz— y no por costumbre ni por moda.
- Toma un componente React tuyo con
useStateyuseEffectcon array de dependencias. - Reescríbelo en Solid:
createSignal, un cuerpo que corre una vez yonCleanuppara limpiar. - Identifica qué desapareció: el array de dependencias, algún
useCallback, quizá unmemo. - Anota qué bug de closure obsoleta ya no puede ocurrir en la versión Solid.