Saltar al contenido

Tu primer componente reactivo: el contador

Un contador construido paso a paso con createSignal, y la explicación exacta de por qué reacciona: el componente corre una vez, el getter count() leído dentro del JSX se suscribe a la señal, y el setter dispara el efecto de render que actualiza solo ese nodo de texto.

El contador, línea a línea

createSignal es el primitivo fundacional de Solid. Recibe un valor inicial y devuelve una tupla de dos funciones: un getter que lee el valor —y, si estás dentro de un contexto reactivo, se suscribe a él— y un setter que lo cambia y notifica a quien lo observaba.

JSX
import { createSignal } from "solid-js";function Contador() {  const [count, setCount] = createSignal(0);  return (    <button onClick={() => setCount(count() + 1)}>      Has pulsado {count()} veces    </button>  );}

Fíjate en que count es una función: se lee invocándola, count(), no accediendo a una variable. Esa llamada no es un capricho de sintaxis; es la que permite el rastreo. Y setCount acepta tanto un valor nuevo como una función que recibe el valor previo, forma que conviene cuando la actualización depende del estado anterior.

JSX
// dos formas de incrementar; la segunda es mas segura ante concurrenciasetCount(count() + 1);setCount((n) => n + 1);

La tupla es solo una convención: createSignal devuelve un array de dos posiciones que desestructuras con los nombres que quieras. Nada te obliga a llamarlos count y setCount; lo esencial es el par lectura/escritura. Separarlos —en vez de exponer un objeto mutable— es precisamente lo que permite a Solid saber con exactitud cuándo lees y cuándo escribes.

El componente corre una vez

Aquí está la idea que lo cambia todo, y la que más cuesta a quien viene de React. El cuerpo de Contador —la línea del createSignal, el return— se ejecuta una sola vez, cuando el componente se crea. Nunca se vuelve a llamar. No hay ciclo de render, no hay reejecución de la función en cada cambio.

¿Cómo cambia entonces el número en pantalla si la función no se repite? Porque lo que reacciona no es el componente, sino un fragmento diminuto que el compilador extrajo del JSX. Recuerda lo que genera Solid: la parte estática del botón se hornea en una plantilla, y {count()} se convierte en un insert() que crea un efecto de render. Ese efecto —y solo ese— es lo que vuelve a correr cuando la señal cambia.

Por qué reacciona: la suscripción

El mecanismo es una danza de dos pasos entre lectura y escritura, sin magia y sin diffing.

La lectura suscribe. Cuando el efecto de render que envuelve {count()} se ejecuta por primera vez, invoca count(). En ese instante, Solid tiene un efecto “activo” apuntado en una variable global interna, y el getter, al ejecutarse, mira ahí y se anota: “este efecto depende de mí”. Así se construye la arista del grafo, automáticamente, por el mero hecho de leer dentro de un contexto de rastreo. No declaras dependencias en una lista; la lectura es la declaración.

La escritura notifica. Cuando pulsas el botón y setCount corre, la señal compara el valor nuevo con el viejo; si difieren, recorre su lista de suscriptores y los marca para reejecutarse. El efecto de render vuelve a correr, invoca count() de nuevo —ahora devuelve el valor incrementado— y actualiza el nodo de texto. Nada más se toca: ni el <button>, ni el texto fijo “Has pulsado”, ni el resto de la página.

Un corolario importante: la suscripción solo ocurre si lees la señal dentro de un contexto de rastreo —un efecto de render, un createEffect, un createMemo—. La misma llamada count() en el cuerpo del componente, que corre una vez y fuera de todo efecto, lee el valor pero no suscribe a nadie. Por eso el número del JSX reacciona mientras que una copia guardada en una variable al montar se queda congelada: no importa dónde vive el valor, importa desde dónde lo lees.

Y una precisión sobre la escritura: si cambias varias señales de golpe en un mismo manejador, Solid agrupa las notificaciones y ejecuta cada efecto afectado una sola vez al final, no una por cada set. Esa agrupación —el batching— evita estados intermedios visibles y trabajo redundante, y en el Solid actual sucede de forma automática dentro de los manejadores de eventos.

Este último punto merece una demostración. Un valor derivado es simplemente una función que lee la señal; puedes usarla en el JSX y reaccionará igual, porque hereda la suscripción de la señal que consume.

JSX
function Contador() {  const [count, setCount] = createSignal(0);  const doble = () => count() * 2;          // derivado: solo una funcion  return (    <div>      <button onClick={() => setCount((n) => n + 1)}>+1</button>      <p>Valor {count()} y su doble {doble()}</p>    </div>  );}

¿Cuándo conviene createMemo en vez de una función derivada? Solo cuando el cálculo es caro o lo consumen varios lugares: el memo cachea su resultado y recomputa únicamente si sus dependencias cambian, mientras que la función derivada se reevalúa en cada lectura. Para un doble trivial el memo sobra; para filtrar y ordenar una lista grande leída en tres sitios, paga con creces.

Contra el reflejo de React

Poner los dos modelos lado a lado revela filosofías opuestas bajo el mismo contador. En React, pulsar el botón llama a setCount y eso reejecuta la función entera del componente: se fabrica un nuevo árbol virtual, se compara con el previo y se parchea la diferencia. El componente es una función del estado que se evalúa una y otra vez.

En Solid no sucede nada de eso. setCount no vuelve a llamar a Contador —su cuerpo corrió una vez y no volverá—; solo notifica al efecto de render que leyó la señal. No se crea ningún elemento, no se compara ningún árbol, no se reevalúa el <button> ni el texto fijo. De ahí que en Solid no existan ni hagan falta useMemo, useCallback ni los arrays de dependencias: no hay reejecución de la que defenderse, así que no hay nada que memoizar por precaución.

JSX
// anti-patron: la condicion se evalua una sola vez, al montarfunction Mal(props) {  if (props.n > 10) return <p>Muchos</p>;   // no reacciona a props.n  return <p>Pocos</p>;}// correcto: la lectura reactiva vive DENTRO del JSXfunction Bien(props) {  return <p>{props.n > 10 ? "Muchos" : "Pocos"}</p>;}

La regla de oro que se deduce: en Solid la reactividad vive dentro del JSX y de los efectos, donde las lecturas quedan rastreadas, no en el cuerpo del componente, que corre una vez. Un if en la raíz decide para siempre; la misma condición dentro del JSX se reevalúa cada vez que su señal cambia.