DEV Community

Franklin
Franklin

Posted on

Por qué JavaScript debería mutar el estado en lugar de los estilos

Also available in Inglés

El problema

Un menú desplegable debe abrirse. Un modal debe aparecer. Un enlace de navegación debe mostrar en qué sección se encuentra el lector actualmente.

La ruta directa es element.style.display = 'block': el estado y la apariencia se deciden en la misma línea, sin dejar nada en manos de CSS. La mayoría de los desarrolladores experimentados ya evitan esto. El patrón más común es usar una clase en su lugar: classList.toggle('is-open'). Eso es una mejora real y una abstracción de estado verdadera: CSS decide cómo se ve .is-open, JavaScript simplemente alterna si la clase está presente o no.

Aquí es donde se pone más interesante. .is-open se extrae del mismo espacio de nombres que cualquier otro gancho de estilo en la base de código. Nada en la sintaxis lo marca como estado en lugar de apariencia. Un compañero de equipo dentro de seis meses, al examinar la hoja de estilos, no tendrá forma de distinguir .is-open de .card-title, excepto al interpretar la intención en el nombre y confiar en que se haya mantenido la convención.

Por qué existe el problema

Los cambios basados en clases funcionan, y lo hacen por convención: el equipo acuerda, de manera informal, que las clases con el prefijo is- o has- significan estado, y todo lo demás significa apariencia. Ese acuerdo es real, y en un equipo disciplinado se mantiene durante mucho tiempo.

También es exactamente la forma de fragilidad que esta serie ya ha diagnosticado. Los límites basados en convenciones sobreviven mientras las personas que los acordaron sigan estando allí para imponerlos. Nada en la sintaxis de una clase impide que se reutilice como una utilidad, que sea capturada por una disputa de especificidad de la misma manera que cualquier otro selector, o que simplemente sea malinterpretada por alguien nuevo en la base de código que nunca ha visto documentada la convención is- en ninguna parte.

quell-light.js da un paso más allá de la convención. Su propio encabezado establece el límite claramente, antes de una sola línea de implementación: no es una biblioteca de componentes, no renderiza nada y su único mandato es la estabilización del comportamiento. La parte interesante no es que evite los estilos en línea; la mayoría del código serio ya lo hace. Es lo que busca en lugar de una clase de estado.

El primer principio

El estado debe representarse como estado: legible como estado en la propia sintaxis, no solo por convención.

Una clase puede hacer esto. data-q-active lo hace de forma más explícita, porque un atributo data-* no tiene un significado de estilo con el cual competir. No se puede confundir con una clase de utilidad, no puede verse involucrado en una disputa de especificidad no relacionada y no depende de que una convención de nomenclatura a nivel de equipo se mantenga durante años a través de cada colaborador que toque el archivo. El vocabulario de estado completo de quell-light.js (data-q-toggle, data-q-active, data-q-dismiss, data-q-spy, data-q-current) describe únicamente lo que es verdad. Nada de esto describe la apariencia. CSS posee eso por completo, a través de selectores de atributos ordinarios que reaccionan a cualquiera de estos que esté presente.

Demostración del principio

Un cambio basado en clases, el punto de partida común y defendible:

trigger.addEventListener('click', () => {
  target.classList.toggle('is-open');
});
Enter fullscreen mode Exit fullscreen mode
.is-open { display: block; }
Enter fullscreen mode Exit fullscreen mode

Separación real. CSS sigue siendo dueño de la apariencia. Pero .is-open se encuentra en el mismo espacio de selectores que todo lo demás en la hoja de estilos, y nada lo marca como diferente.

La versión de atributo, más definida por diseño:

trigger.addEventListener('click', () => {
  trigger.setAttribute('aria-expanded', 'true');
  target.setAttribute('data-q-active', '');
});
Enter fullscreen mode Exit fullscreen mode
[data-q-active] { display: block; }
Enter fullscreen mode Exit fullscreen mode

Mismo resultado. La diferencia es lo que la propia sintaxis garantiza: data-q-active no puede derivar en significar otra cosa, porque nunca fue elegible para significar otra cosa.

quell-light.js como caso de estudio

El módulo de divulgación anterior es cercano al mecanismo real en el archivo, menos el manejo de grupos exclusivos y la delegación de eventos. Es la prueba más clara de que esta disciplina se sostiene bajo un requisito de accesibilidad real: un control interactivo accesible necesita tanto un estado ARIA como un gancho estilizable, y el archivo los mantiene como dos atributos explícitas en lugar de una sola clase sobrecargada.

Los otros dos módulos interactivos mantienen la misma línea bajo condiciones más difíciles. La gestión del foco impone un contenedor estricto de Tab/Shift+Tab dentro de un diálogo abierto y rastrea a qué elemento devolver el foco al cerrar: estado real, con la apariencia del diálogo dejada completamente a CSS. El observador de desplazamiento utiliza IntersectionObserver en lugar de un receptor de desplazamiento directo para decidir qué sección está activa, expresado como un único aria-current en un enlace de navegación a la vez; de nuevo, una decisión de estado, renderizada como la hoja de estilos considere oportuno.

La lección más amplia

Esto define de forma más precisa el límite del artículo 4, en lugar de simplemente reafirmarlo. La afirmación nunca fue que las clases estuvieran mal. Es que un límite impuesto por convención es solo tan duradero como la memoria del equipo sobre dicha convención, y un límite codificado directamente en la sintaxis no tiene esa dependencia. quell-light.js no evitó un error. Eligió la versión más explícita de algo que ya valía la pena hacer.

Top comments (0)