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.
Cambiar el aspecto de un nodo según el estado es el pan de cada día, y Solid ofrece cuatro formas de hacerlo: classList con un objeto, style con un objeto, y los espacios de nombres finos class: y style:. No son intercambiables: cada una crea un número distinto de efectos reactivos y produce un número distinto de escrituras al DOM. Elegir bien es la diferencia entre una animación fluida y una que reflow constantemente.
- Alternar varias clases con el objeto de
classListy entender su diffing. - Escribir estilos dinámicos con el objeto de
style, nombres CSS y variables. - Usar los espacios finos
class:nombreystyle:propiedadpara máxima granularidad. - Razonar el coste: cuántos efectos y cuántas escrituras al DOM genera 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).
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.
El patrón idiomático es una class estática con las clases que siempre están, y classList (o class:) para las que se alternan. Evita poner una class reactiva que cambie: al reescribir el atributo entero puede pisar lo que gestiona classList. Base fija en class, dinámica en classList, y nunca la misma clase en ambos sitios.
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.
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.
<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:
<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:
<div class:activa={on()} /> // nombre fijo, efecto propio
<div classList={{ [claseDinamica()]: on() }} /> // nombre calculado en runtime
<div style={{ [propCSS()]: valor() }} /> // idem para propiedades
flowchart TD
V[Valor reactivo de estilo] --> O{Como lo enlazas}
O -->|objeto classList| P[Un efecto que diffea todas las clases juntas]
O -->|objeto style| Q[Un efecto que diffea todas las propiedades]
O -->|namespace class| R[Un efecto propio por cada clase]
O -->|namespace style| S[Un efecto propio por cada propiedad]
P --> W[Escrituras al DOM minimas solo lo que cambio]
Q --> W
R --> W
S --> W
style R fill:#a6e3a1,color:#11111b
style S fill:#a6e3a1,color:#11111b
style P fill:#89b4fa,color:#11111b
style Q fill:#89b4fa,color:#11111bEl 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.
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 })}
/>
);
}
Reserva el style inline para valores continuos que no puedes conocer de antemano: una posición calculada, un transform que sigue al ratón, una altura medida. Para estados discretos y conocidos (activo, error, seleccionado) prefiere alternar clases: el navegador cachea mejor las reglas CSS, la especificidad es más manejable y separas presentación de lógica. Y para animar, quédate en transform y opacity, que la GPU compone sin tocar el layout, en vez de animar width o left.
La lección profunda es que en Solid hay dos granularidades distintas y no debes confundirlas. Está la granularidad reactiva —cuántos efectos se crean y qué re-ejecuta cada cambio— y está la granularidad de escritura al DOM —cuántos setProperty acaban ocurriendo—. Las formas de objeto, classList y style, agrupan varias propiedades bajo un solo efecto: un cambio en cualquiera re-corre el diff de todas, pero solo escribe lo que cambió. Los espacios finos, class: y style:, dan un efecto por binding: recomputación aislada, a cambio de más nodos en el grafo. Ninguna es universalmente mejor. Con dos o tres propiedades que suelen cambiar juntas, el objeto es más legible y su coste de re-diff es trivial. Con una propiedad que cambia a 60 fps mientras otras diez permanecen quietas, el espacio fino evita re-evaluar las diez en cada frame. Pensar así —separar el grafo reactivo de las mutaciones del DOM, y ambos del coste de reflow del navegador— es lo que distingue un ajuste de rendimiento con criterio de un cambio a ciegas.
- Crea un componente con un objeto
classListde tres estados y comprueba en las DevTools que al alternar uno solo se añade o quita esa clase. - Reescríbelo con tres
class:independientes y observa que el resultado en el DOM es idéntico. - Anima una barra con
style:transform={translateX(...)}a 60 fps y compárala con animarstyle:left: mide el reflow en el panel de rendimiento. - Define una variable CSS
--acentoreactiva constyley úsala desde tu hoja de estilos; cámbiala y verifica que todo lo que la consume se actualiza sin tocar cada nodo. - Provoca a propósito el antipatrón: una
classreactiva más unclassListcon una clase repetida, y observa cómo se pisan.