Refs al DOM: la variable, el callback y su timing
Cómo captura Solid un nodo del DOM con la variable local ref y con el callback ref, cómo el compilador transforma ref en una asignación durante la creación del elemento, y el timing exacto: el ref se resuelve al crear el nodo, antes de insertarlo, por lo que existe como objeto pero aún no está en la página. Diferencias de timing con React y el patrón ref reactivo con un setter de signal.
Dos formas de capturar, un mismo instante
La forma más común es una variable local. El compilador reconoce ref={identificador} y, en lugar de leer la variable, genera código que le asigna el nodo en cuanto lo crea:
import { onMount } from "solid-js";function Editor() { let campo!: HTMLInputElement; // ! promete a TS que se asignara antes de usarse onMount(() => campo.focus()); // aqui el nodo ya esta en el documento return <input ref={campo} placeholder="escribe" />;}Se usa let, no const, porque Solid va a escribir en la variable. La otra forma es un callback: recibe el elemento como argumento en el mismo instante de la creación. Es lo natural cuando quieres reaccionar al nacer el nodo o guardarlo en una estructura dinámica.
<For each={items()}> {(item) => <li ref={(el) => registrar(item.id, el)}>{item.nombre}</li>}</For>Lo esencial es que ambas formas se resuelven en el mismo momento: cuando el elemento se construye. La variable y el callback no son dos timings distintos; son dos maneras de recibir el mismo nodo en el mismo punto de la línea temporal.
El compilador convierte ref en una asignación
ref no es una prop que el elemento reciba en runtime, sino una instrucción para el compilador. Cuando Solid ve ref={campo}, no evalúa la expresión como valor: la trata como un destino de escritura. Lo que emite se parece conceptualmente a esto:
// Lo que escribes:<input ref={campo} />;// Lo que el compilador emite, en esencia:const _el = document.createElement("input");campo = _el; // asignacion inmediata, en la creacion// ... mas tarde el padre hace parent.appendChild(_el)Por eso la variable debe ser un let y por eso ref={campo} y ref={(el) => ...} son la misma operación vista de dos formas: en el primer caso el compilador asigna a tu variable; en el segundo, invoca tu función con el nodo. En ambos, la línea que captura corre entre crear el elemento e insertarlo.
Crear no es insertar
Aquí está el matiz que hay que grabar. En el instante en que el ref se resuelve, el nodo existe como objeto en memoria, pero su parentNode puede ser null y cualquier medida de layout —offsetWidth, getBoundingClientRect— devuelve cero, porque el elemento todavía no forma parte de la página.
Los cuatro instantes de la vida temprana de un nodo, en orden:
La regla operativa es tajante: usa el ref para capturar el nodo, y onMount para usarlo cuando ya está adjunto. Todo lo que dependa de estar en el documento —enfocar, medir, arrancar un ResizeObserver— va dentro de onMount, nunca junto a la asignación del ref.
function Lienzo() { let canvas!: HTMLCanvasElement; onMount(() => { const rect = canvas.getBoundingClientRect(); // ya insertado: mide de verdad const ctx = canvas.getContext("2d")!; ctx.fillRect(0, 0, rect.width, rect.height); }); return <canvas ref={canvas} />;}Leer el ref en el cuerpo del componente es el error simétrico: el cuerpo corre antes de que el JSX se evalúe, así que ahí la variable aún vale undefined. El nodo no existe todavía en ninguna forma.
Cuando necesitas que el nodo sea reactivo —que otros cómputos reaccionen a que ya existe—, la variable let no basta, porque asignarla no notifica a nadie. El patrón entonces es pasar el setter de un signal directamente como ref:
import { createSignal, createEffect } from "solid-js";function Panel() { const [caja, setCaja] = createSignal<HTMLDivElement>(); createEffect(() => { const el = caja(); if (!el) return; // undefined hasta que el nodo se crea y asigna el.scrollIntoView(); }); return <div ref={setCaja}>...</div>;}Como setCaja es una función, encaja en la forma de callback: Solid lo invoca con el nodo al crearlo, el signal pasa de undefined al elemento, y el efecto que lo lee se re-ejecuta. Es la versión reactiva de la captura, útil cuando el “ya existe el nodo” debe propagarse por el grafo.
En resumen: usa la variable let cuando solo necesitas el nodo dentro de onMount, y usa el setter de signal cuando otros cómputos deben despertar en cuanto el nodo aparece. Ambos comparten el mismo instante de captura; se diferencian solo en si notifican al grafo o no.