DEV Community

Franklin
Franklin

Posted on

Por qué los árboles DOM con abstracción de componentes se vuelven más difíciles de leer

Hermano estructural de Por qué toda hoja de estilos grande termina siendo impredecible — misma distinción, diferente capa.

También disponible en Inglés

El problema

Una sección del panel de control se titula "Suscripciones activas". Visualmente, se lee como un encabezado de sección: en negrita, con un tamaño mayor que el texto circundante, ubicado exactamente donde el lector espera encontrar el título de una sección. Pregúntale a un lector de pantalla qué es en realidad, y la respuesta será diferente. Es un encabezado seis niveles por debajo del título de la página, un elemento ubicado en el fondo de la jerarquía de encabezados, describiendo una sección que el lector esperaría encontrar cerca de la parte superior.

Nadie escribió ese nivel de encabezado a propósito. Un desarrollador colocó un componente de tarjeta en el diseño del panel de control y continuó con su trabajo. El componente se veía correcto en la página donde aterrizó. Se veía correcto de forma aislada, en la biblioteca de componentes del propio sistema de diseño, en cada lugar donde alguien se tomó el tiempo de revisarlo. El nivel de encabezado nunca fue una decisión. Fue un valor predeterminado, definido a seis componentes de distancia, heredado por una sección que nunca tuvo voz ni voto.


Por qué existe el problema

La abstracción de componentes existe para resolver un problema real. Sin ella, cada equipo reconstruye la misma tarjeta, el mismo botón, la misma escala tipográfica, y las pequeñas inconsistencias se acumulan a lo largo del código base. Una biblioteca de componentes compartida intercambia esa inconsistencia por una única implementación revisada. La reutilización aumenta. La desviación visual disminuye.

El intercambio ocurre en una costura específica. El autor de un componente escribe código una vez, para usarse en lugares que no puede predecir por completo. Para hacer esto posible, el componente debe tomar decisiones por sí mismo: cómo se ve, cómo se comporta, qué renderiza, todo sin saber exactamente dónde aterrizará en la página. Eso no es un defecto en el diseño — es la premisa misma de la reutilización.

La mayoría de esas decisiones locales son genuinamente seguras de tomar. El relleno (padding) de un botón no depende de dónde se ubique el botón en la página. El radio de borde de una tarjeta tampoco. Pero un pequeño número de decisiones no son locales en absoluto. Dependen del contexto del documento que nunca se le dio al componente. El nivel de un encabezado es una de ellas. Si el título de una sección debe renderizarse como un <h2> o un <h6> no es una propiedad de la sección. Es una propiedad del lugar donde se ubica la sección dentro del esquema de la página — un hecho que el componente que renderiza el encabezado usualmente nunca ve.


El primer principio

El navegador recibe el DOM que produjeron los componentes: una jerarquía de elementos sin memoria de qué archivo fue autor de qué nodo, sin una frontera que marque dónde termina la salida de un componente y dónde comienza la del siguiente. Seis componentes pueden colaborar para renderizar un encabezado. El navegador lee un solo <h6>.

Los niveles de encabezado en HTML no se infieren a partir de la profundidad del componente o de la sección. Un <h6> anidado a tres secciones de profundidad sigue siendo exactamente lo que dice su nombre de etiqueta, leído en el orden en que aparecen los encabezados en la página. La lista de encabezados de un lector de pantalla se construye a partir de esos nombres de etiquetas, en el orden del documento — no a partir de qué componente los escribió. No tiene forma de preguntar qué componente pretendía decir algo diferente.

Lo que convierte al navegador en el punto donde cada decisión componenteizada colapsa en un único hecho plano. Seis capas de intención se resuelven en una sola etiqueta. La capa que en última instancia defina esa etiqueta se vuelve responsable del nivel de encabezado, tenga o no la información necesaria para definirlo correctamente.

Demostrando el principio

Reduce el patrón a su forma más pequeña. Un componente de diseño (layout) sabe dónde se ubica una sección en la página. Un componente de tipografía sabe cómo debe verse un encabezado.

function Section({ title, children }) {
  return (
    <section>
      <Heading variant="subheading">{title}</Heading>
      {children}
    </section>
  );
}

function Heading({ variant, children }) {
  const Tag = variant === 'subheading' ? 'h6' : 'h2';
  return <Tag>{children}</Tag>;
}
Enter fullscreen mode Exit fullscreen mode

Renderizado en una página después de un <h1>, esto produce:

<main>
  <h1>Dashboard</h1>
  <section>
    <h6>Active Subscriptions</h6>
  </section>
</main>
Enter fullscreen mode Exit fullscreen mode

Section sabe que está dentro de un panel de control, después de un <h1>. Nunca ve como qué etiqueta se renderiza su encabezado. Heading sabe exactamente qué etiqueta renderiza, decidido enteramente por una prop de variante. No tiene idea de en qué parte de la página se está utilizando. Entre los dos, ninguno de los componentes posee ambas piezas de información que la decisión realmente necesitaba: la posición en la página y el nombre de la etiqueta.

La solución no es eliminar una capa. Es mover la decisión a cualquier capa que ya tenga la mitad que falta:

function Section({ title, level, children }) {
  const HeadingTag = `h${level}`;
  return (
    <section>
      <HeadingTag>{title}</HeadingTag>
      {children}
    </section>
  );
}
Enter fullscreen mode Exit fullscreen mode

Ahora el componente que conoce la posición en la página también define la etiqueta. El tratamiento visual aún puede residir en otro lugar completamente diferente — una clase, una variante de tamaño, una sobreescritura de peso. Lo que se mueve es el nivel de encabezado en sí, y se mueve al componente que tenía el contexto para definirlo, no al componente que solo sabía cómo debía verse un encabezado.


El punto de dolor

Esto surgió durante una auditoría de la migración del sistema de diseño de un cliente, en un panel de control empresarial ya en producción. Una sección titulada "Suscripciones activas" era visualmente correcta: con el tamaño y peso de cualquier otro encabezado de sección en la página, y seis niveles por debajo del título de la página en la jerarquía de encabezados. Llegar allí requirió seis componentes: un diseño de panel de control, un grupo de tarjetas, una tarjeta, un encabezado de tarjeta, una utilidad tipográfica y una primitiva de encabezado que mapeaba una prop de variante a un nombre de etiqueta.

Rastrear el nivel real de encabezado significó leer los seis componentes. El diseño del panel de control era dueño del esquema de la página y no tenía visibilidad de lo que renderizaba cada tarjeta individual. La primitiva de encabezado era dueña del nombre de etiqueta y no tenía visibilidad de en qué parte de la página había aterrizado. Cada capa intermedia transmitió la decisión sin tocarla. Ningún componente individual estaba roto. Cada uno hizo exactamente aquello para lo que fue construido. El nivel de encabezado simplemente no era una decisión que ninguno de ellos estuviera posicionado para tomar correctamente, porque ninguno de ellos poseía el contexto de la página y el nombre de la etiqueta al mismo tiempo.

Saturday's Notes from the Pass recorre ese mismo rastro de seis capas, marca el punto donde la decisión estructural y el contexto del documento que necesitaba se separaron, y compara dos formas de volver a unirlos — sin colapsar los seis componentes en uno solo.


La lección más amplia

Esto no es un argumento en contra de la abstracción de componentes. Cada razón por la que existen los componentes es una razón real, y nada de eso desaparece porque un encabezado haya terminado en la etiqueta equivocada. El fallo no está en dividir una página en componentes. Está en dividir una decisión entre componentes sin verificar si a alguno de ellos le quedó suficiente información para tomarla.

Cada capa de abstracción, en cualquier sistema, traza un límite alrededor de lo que esa capa necesita saber. La mayoría de las veces el límite es exactamente el correcto: la capa no necesita el resto de la imagen, y ocultárselo a esa capa es todo el propósito. En ocasiones, una decisión no respeta el límite de la forma en que la arquitectura asumió que lo haría. Necesita información de ambos lados a la vez, y cuando ninguno de los lados es dueño de toda la decisión, la decisión se toma de todos modos, por la capa que toque el asunto en último lugar.

La arquitectura de componentes puede organizar el código fuente. No puede reemplazar la estructura del documento. Ambos responden a consumidores diferentes: uno al desarrollador que abre el archivo, el otro al navegador y a todo aquello a lo que el navegador entregue esa decisión después.

Top comments (0)