Saltar al contenido

classList y style reactivos

Alternar clases con un objeto en classList, estilos dinámicos con objetos de propiedades CSS y variables, los espacios de nombres finos class: y style:, y el modelo de coste real: qué efecto se crea y cuántas escrituras al DOM produce cada forma.

classList: alternar clases por objeto

Cuando una clase depende del estado, la tentación es concatenar strings. Solid ofrece algo mejor: classList toma un objeto donde las claves son nombres de clase y los valores son booleanos (o accesores que devuelven booleanos).

TSX
function Pestana(props: { activa: boolean; deshabilitada: boolean }) {  return (    <button      class="pestana"                          // clase base estatica      classList={{        "pestana--activa": props.activa,        // se anade o quita segun el booleano        "pestana--off": props.deshabilitada,        "pestana--activa pestana--grande": props.activa,  // varias clases por clave      }}    >      {props.children}    </button>  );}

Solid compila classList a un efecto que diferencia el objeto nuevo contra el anterior. Solo toca el DOM para las clases que cambiaron de estado: si activa pasa de true a false pero deshabilitada no cambia, únicamente se elimina pestana--activa, sin tocar el resto. Una clave puede contener varias clases separadas por espacio, y Solid las gestiona todas juntas.

style: objetos, nombres CSS y variables

El binding style acepta un objeto, pero con dos diferencias cruciales respecto a React. Primero, las claves son nombres CSS reales en kebab-case, no camelCase: se escribe background-color, no backgroundColor. Segundo, Solid usa element.style.setProperty, lo que te habilita las variables CSS de forma nativa y reactiva.

TSX
function Barra(props: { progreso: number; color: string }) {  return (    <div      class="barra"      style={{        width: `${props.progreso}%`,       // valor numerico dinamico, con unidad        "background-color": props.color,     // nombre CSS en kebab-case        "--brillo": props.progreso > 80 ? "1.2" : "1", // variable CSS reactiva      }}    />  );}

Igual que classList, el style de objeto se compila a un efecto que lee todas las propiedades. Cuando cualquiera de ellas cambia, el efecto se re-ejecuta, reconstruye el objeto y hace un diff contra el anterior, aplicando setProperty (o removeProperty) solo a las claves que de verdad cambiaron. Un string en style también vale, pero entonces cada cambio reescribe el atributo completo: pierdes el diffing.

Las variables CSS reactivas brillan precisamente aquí: expones un -- en un contenedor y dejas que la hoja de estilos lo consuma, actualizando muchos nodos con una sola escritura.

TSX
<section style={{ "--acento": tema().acento }}>  {/* cualquier regla que use var(--acento) reacciona sin tocar cada nodo */}</section>

Los espacios finos: class: y style:

Para la máxima granularidad reactiva existen dos espacios de nombres que enlazan una sola clase o propiedad:

TSX
<div  class:activa={esActiva()}          // un efecto propio, solo depende de esActiva  class:cargando={estaCargando()}    // otro efecto independiente  style:color={color()}              // un efecto por propiedad  style:transform={`translateX(${x()}px)`}/>

La diferencia con las formas de objeto es de granularidad de seguimiento. classList={{a, b}} crea un efecto que depende de a y de b: si cambia a, el efecto entero se re-ejecuta y re-diffea ambas. class:a y class:b crean dos efectos independientes: cambiar a solo re-ejecuta el suyo. En la escritura al DOM ambas son mínimas; la diferencia está en cuánto trabajo de recomputación desencadena cada cambio.

Un límite decide por ti cuál usar: class: y style: exigen un nombre estático —es parte de la sintaxis del binding—, así que para nombres calculados en runtime necesitas la forma de objeto con clave dinámica:

TSX
<div class:activa={on()} />                      // nombre fijo, efecto propio<div classList={{ [claseDinamica()]: on() }} />   // nombre calculado en runtime<div style={{ [propCSS()]: valor() }} />          // idem para propiedades

El coste real y cuándo importa

Todas estas formas ya hacen lo correcto: escrituras mínimas al DOM. La optimización de la que se habla menos no es cuántas propiedades escribes, sino cuánto reflow provocas. Cambiar clases que solo afectan a color u opacity es barato; cambiar width, top o cualquier propiedad de layout fuerza al navegador a recalcular la geometría de la página.

El caso legítimo del style fino es justo ese valor continuo que no conoces de antemano: una posición que sigue al puntero, animada con transform para que la GPU la componga sin tocar el layout.

TSX
import { createSignal } from "solid-js";function Halo() {  const [pos, setPos] = createSignal({ x: 0, y: 0 });  return (    <div      class="halo"      style:transform={`translate(${pos().x}px, ${pos().y}px)`} // solo composite      onPointerMove={(e) => setPos({ x: e.clientX, y: e.clientY })}    />  );}