Binding: atributos, propiedades y spread
Cómo liga Solid los valores al DOM: la distinción entre atributo HTML y propiedad del nodo, los namespaces attr:, prop: y bool: para forzar una u otra, el spread reactivo de props, las expresiones que se re-ejecutan en atributos, y el tratamiento de los booleanos.
Atributo no es lo mismo que propiedad
Un elemento del DOM tiene dos superficies que se parecen y no coinciden. Los atributos son lo que escribes en el HTML —cadenas de texto, siempre—; las propiedades son campos del objeto JavaScript que representa al nodo, y pueden ser de cualquier tipo. setAttribute("value", "hola") fija el valor inicial; la propiedad .value refleja el valor actual, que el usuario puede haber cambiado al teclear. No son sinónimos: divergen en cuanto hay interacción.
Solid conoce, para los elementos HTML estándar, qué nombres son propiedades reales y cuáles atributos, y elige la vía correcta automáticamente. Por eso value, checked o textContent se asignan como propiedades, mientras que class, id o data-* se escriben como atributos. En el 95% de los casos no piensas en esto.
// Solid decide: value como propiedad, class como atributo<input value={texto()} class={clase()} />Esta distinción es justo la que hace funcionar un input controlado. Al ligar value como propiedad, Solid escribe el campo vivo del nodo —el mismo que el usuario altera al teclear—; si usara el atributo, solo fijaría el valor inicial y el campo se desincronizaría en cuanto alguien escribiese. En la práctica es la diferencia entre un formulario que responde y uno que parece congelado pese a que el estado cambia. El reverso también instruye: class, id o los data-* no tienen una propiedad viva que el usuario modifique, así que se escriben como atributos y ahí permanecen. Solid mantiene internamente la tabla de qué nombres son propiedades reales de cada elemento HTML, y es esa tabla la que consulta para decidir.
El problema aparece en la frontera: componentes web y atributos personalizados, donde Solid no puede saber tu intención. Para eso están los namespaces explícitos.
Forzar la vía: attr:, prop: y bool:
Cuando la heurística no basta, los namespaces mandan sobre ella:
// un componente web que espera datos por propiedad y una marca por atributo<mi-grafica prop:datos={serie()} attr:data-tema={tema()} />Estos tres no agotan la lista: on: y oncapture: atan eventos nativos, use: invoca directivas personalizadas y ref captura el nodo. Todos comparten la misma naturaleza —una instrucción explícita de cómo ligar— y todos se resuelven en tiempo de compilación, no en ejecución.
El spread reactivo de props
Solid soporta el spread {...props} en el JSX, y aquí ocurre algo que sorprende a quien viene de React: el spread sigue siendo reactivo. El compilador no copia los valores una vez; genera una ligadura que vigila el objeto y actualiza cada atributo o propiedad cuando su fuente cambia.
function Boton(props) { // reparte todas las props sobre el nodo, y cada una queda ligada return <button {...props}>Aceptar</button>;}Esto es posible porque las props de Solid no son un objeto plano: son un objeto con getters reactivos. Leer props.disabled dentro de un efecto suscribe a ese campo; el spread hace exactamente eso por cada clave. La consecuencia práctica es doble. Primero, nunca destructures las props en la firma —function Boton({ disabled })— porque eso lee los getters una vez y congela su valor, rompiendo la reactividad. Segundo, para combinar props con valores propios sin perder reactividad usas mergeProps, no el spread de objetos de JavaScript.
import { mergeProps } from "solid-js";function Boton(props) { // valores por defecto sin romper la reactividad de las props const c = mergeProps({ tipo: "button" }, props); return <button type={c.tipo} disabled={c.disabled} />;}Su complemento es splitProps, la forma correcta de “descomponer” props sin congelarlas: parte el objeto en grupos que conservan sus getters. Es lo que usas para consumir unas props tú y reenviar el resto con el spread.
import { splitProps } from "solid-js";function Campo(props) { // separa las props propias del resto, sin perder reactividad const [propias, resto] = splitProps(props, ["etiqueta"]); return ( <label> {propias.etiqueta} <input {...resto} /> </label> );}Expresiones reactivas y booleanos
Cualquier expresión que pongas en un atributo y que lea un signal se vuelve reactiva: el compilador la envuelve en un efecto que la reevalúa cuando su dependencia cambia. No pasas la función del signal, la invocas dentro del JSX; el compilador se encarga de que esa lectura quede rastreada.
// cada atributo se reevalua cuando su signal cambia<a href={`/perfil/${id()}`} title={etiqueta()} tabindex={activo() ? 0 : -1} />Los atributos booleanos —disabled, checked, readonly, hidden— reciben un trato especial gobernado por la veracidad del valor. Si la expresión es verdadera, Solid pone el atributo o la propiedad; si es falsa, lo quita. No escribes la cadena "true"; pasas un booleano y Solid traduce presencia y ausencia.
// disabled aparece o desaparece segun el booleano<button disabled={enviando()}>Guardar</button>// para atributos no estandar, bool: hace lo mismo de forma explicita<div bool:data-abierto={abierto()} />Un matiz que revela la granularidad de Solid: cada expresión reactiva de un atributo genera su propia suscripción. En <a href={u()} title={t()} />, cambiar u() no reevalúa title, y al revés. Los objetos classList y style afinan aún más, comparando clave a clave, de modo que activar una clase no toca a las demás. La unidad reactiva no es el elemento: es cada binding por separado.