Tema representativo de entrevista

Entrevista frontend: usar ResizeObserver sin layout thrashing

FrontendIntermedio
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Una tarjeta de panel redimensionable cambia de diseño según el ancho de su contenedor. ¿Cómo usarías ResizeObserver sin layout thrashing, bucles de retroalimentación, suscripciones duplicadas ni actualizaciones tras el desmontaje?

Planteamiento y alcance

Una tarjeta de panel cambia de diseño en función de su propio contenedor y no del viewport. La primera implementación lee y escribe estilos de forma síncrona dentro de una devolución de llamada de ResizeObserver, lo que provoca fotogramas lentos y errores ocasionales de bucle. Diseña la observación, la medición, la actualización y la limpieza. Las habilidades fundamentales son el layout del navegador y los ciclos de vida de los componentes, por lo que esto pertenece al ámbito de frontend.

Qué evalúa el entrevistador

La respuesta debe distinguir las media queries del viewport de la observación de elementos y cubrir los tiempos de las devoluciones de llamada, la elección del tipo de caja, la separación de lecturas y escrituras, la protección contra bucles, las actualizaciones por lotes, React Strict Mode, múltiples objetivos, elementos ocultos, SSR y accesibilidad.

Preguntas para aclarar primero

  • ¿Observamos la caja de contenido (content box), la caja de borde (border box) o la caja de contenido en píxeles de dispositivo (device-pixel content box)?
  • ¿La actualización modifica clases, variables CSS, dimensiones de Canvas o la geometría del DOM?
  • ¿La devolución de llamada modificará el tamaño del elemento observado o el de un ancestro?
  • ¿Cuántos objetivos existen y se deben agrupar las actualizaciones en el siguiente fotograma?
  • ¿Cómo se gestionan la creación y la limpieza del observador al desmontar, en estado oculto y en SSR?

Marco de respuesta de 30 segundos

“Crea un único observador estable en el cliente y observa la caja requerida. En la devolución de llamada, recopila primero los tamaños y luego separa las lecturas de las escrituras del DOM. Aplica una clase o variable CSS en un fotograma por lotes para que el cambio de diseño no retroalimente de inmediato al objetivo observado. Ignora los valores sin cambios; al desmontar, detén las actualizaciones, deja de observar (unobserve) y desconecta (disconnect). Prueba tareas largas, recuento de layouts, errores de bucle y remontajes en Strict Mode”.

Solución paso a paso

ResizeObserver observa la caja de contenido o de borde de un elemento, de forma independiente del viewport. Elige la caja de manera deliberada: los puntos de interrupción de diseño suelen usar el tamaño de contenido o de borde, mientras que el dibujo con precisión de píxeles puede usar el contenido en píxeles de dispositivo. No leas repetidamente propiedades que fuercen el layout dentro de la devolución de llamada.

Primero, recopila el tamaño más reciente de cada entrada en un pequeño conjunto pendiente y luego calcula los puntos de interrupción en conjunto. Mantén las lecturas de geometría en un solo lote y escribe las variables CSS o clases tras el cálculo. Si la escritura puede afectar al layout, prográmala con requestAnimationFrame y compara el siguiente lote con el valor anterior.

Los bucles de retroalimentación ocurren cuando se “observa el tamaño, se escribe el estilo, se vuelve a cambiar el tamaño”. Por ejemplo, el ancho cambia el padding y el padding cambia el ancho. Añade histéresis, restricciones fijas de contenedor o actualiza propiedades que no alteren el objetivo medido. Si es necesario redimensionar, limita el trabajo a una actualización por fotograma y detecta la convergencia. El navegador puede posponer las notificaciones que no convergen y reportar un error de bucle; ignorar el error no es una solución.

En React, crea el observador en un useLayoutEffect exclusivo del cliente o en un efecto adecuado, con una referencia y una devolución de llamada estables. Strict Mode puede ejecutar la configuración y la limpieza dos veces, por lo que la limpieza debe ser idempotente. Al desmontar, deja de aplicar actualizaciones, deja de observar los objetivos y desconecta. SSR no debe acceder a window ni construir el observador.

Para muchas tarjetas, comparte un observador y mapea entry.target al estado del componente, pero mantén clara la pertenencia para que una tarjeta no bloquee toda la devolución de llamada. Durante un arrastre, conserva solo el tamaño más reciente y fusiona las actualizaciones en un fotograma de animación en lugar de añadir un debounce arbitrario que cause retrasos en el layout.

Los elementos ocultos, display:none, la carga de fuentes y las barras de desplazamiento pueden cambiar el tamaño. Proporciona un valor predeterminado utilizable para una entrada inicial de tamaño cero y cambia tras la primera medición válida. Prefiere las consultas de contenedor de CSS (container queries) para puntos de interrupción puramente de estilo; usa ResizeObserver cuando JavaScript deba controlar Canvas, widgets de terceros o cálculos de negocio.

Verifica con el panel Performance mientras se arrastra, se cargan fuentes, se ocultan y muestran tarjetas, se rota el viewport y se montan muchas instancias. Comprueba que cada cambio de tamaño provoque solo el trabajo necesario, sin bucles persistentes, observadores duplicados, actualizaciones posteriores al desmontaje ni layout thrashing significativo. Prueba también el funcionamiento con teclado y lector de pantalla.

Respuesta modelo de alta calidad

“Creo un ResizeObserver estable en el lado del cliente y elijo intencionalmente la caja de contenido o de borde. La devolución de llamada registra las entradas más recientes y calcula los puntos de interrupción; no lee y escribe layout de forma repetida. Aplico variables CSS o clases en un fotograma por lotes. Si una escritura modifica el tamaño observado, añado histéresis, restricciones y comprobaciones de convergencia para evitar un bucle de retroalimentación.

La limpieza en React es idempotente: detengo las actualizaciones al desmontar, dejo de observar y desconecto. Múltiples tarjetas pueden compartir un observador mediante despacho basado en el objetivo. Realizo pruebas con redimensionamiento por arrastre, carga de fuentes, estados ocultos y visibles, y remontajes en Strict Mode, midiendo layouts, tareas largas, errores de bucle, suscripciones duplicadas e interacción accesible”.

Errores comunes

  • Tratar ResizeObserver como una media query de viewport → los componentes fallan en diferentes contenedores → observar el elemento en sí.
  • Leer y escribir mucho DOM en la devolución de llamada → layout forzado y thrashing → agrupar lecturas y escribir en el siguiente fotograma.
  • Modificar el objetivo observado sin convergencia → bucle de retroalimentación → usar histéresis, restricciones y detección.
  • Crear un observador en cada renderizado → devoluciones de llamada duplicadas y fugas de memoria → usar referencias estables, dependencias y limpieza.
  • Solo usar unobserve pero mantener el estado de la devolución de llamada → las actualizaciones continúan tras el desmontaje → marcar como inactivo, usar unobserve y disconnect.
  • Aplicar el diseño más grande al tamaño cero → el primer pintado da saltos visuales → usar un estado predeterminado hasta obtener una entrada válida.
  • Usar JavaScript para cada punto de interrupción → complejidad innecesaria → usar container queries cuando CSS sea suficiente.
  • Probar solo el arrastre normal → se escapan problemas de fuentes, elementos ocultos y Strict Mode → cubrir el ciclo de vida y los cambios de recursos.

Preguntas de seguimiento y respuestas

Pregunta de seguimiento 1: ¿Cuándo se ejecuta la devolución de llamada?

El navegador agrupa las notificaciones de cambio de tamaño después del layout. Los cambios que no convergen pueden posponerse y generar un error de bucle, por lo que la devolución de llamada no debe provocar cambios de layout ilimitados.

Pregunta de seguimiento 2: ¿Por qué no usar window.resize?

Refleja cambios en el viewport, no cuando una tarjeta cambia debido a una cuadrícula, barra lateral o fuente. ResizeObserver observa el elemento directamente.

Pregunta de seguimiento 3: ¿Cómo se separan las lecturas y las escrituras?

Lee primero los tamaños de las entradas, calcula los resultados y luego agrupa las escrituras de variables CSS o clases en lotes, utilizando requestAnimationFrame cuando sea necesario e ignorando los valores que no hayan cambiado.

Pregunta de seguimiento 4: ¿Un observador por elemento o un observador compartido?

Unos pocos elementos con gestión independiente pueden usar observadores separados. Muchos objetivos pueden compartir uno y despachar por objetivo, siempre que el trabajo de la devolución de llamada se mantenga acotado.

Pregunta de seguimiento 5: ¿Qué sucede después de display:none?

Trata el tamaño cero como temporal, espera una entrada válida después de mostrarse y evita escribir un bucle mientras esté oculto. No trates el cero como un punto de interrupción permanente.

Pregunta de seguimiento 6: ¿Por qué React Strict Mode expone errores?

El modo de desarrollo puede ejecutar la configuración y la limpieza de efectos dos veces para revelar efectos secundarios asimétricos. La limpieza debe ser repetible y no dejar ningún observador o devolución de llamada de actualización pendientes.

Pregunta de seguimiento 7: ¿Cuándo deberían prevalecer las container queries de CSS?

Usa CSS para puntos de interrupción de contenedor puros. Usa ResizeObserver cuando el tamaño deba controlar Canvas, una API de terceros, mediciones complejas o cálculos de negocio.

Fuentes públicas

Preguntas relacionadas