Saltar al contenido principal

[Lv3] Implementación de Virtual Scroll: Manejo de renderizado de grandes volúmenes de datos

Cuándo la página necesita renderizar más de 1000 registros, Virtual Scroll puede reducir los nodos DOM de 1000+ a 20-30, disminuyendo el uso de memoria en un 80%.


Pregunta de entrevista​

P: Si la pantalla tiene más de una tabla, cada una con más de cien registros, y además hay eventos que actualizan el DOM frecuentemente, qué método usarías para optimizar el rendimiento de esta página?


Analisis del problema (Situation)​

Escenario real del proyecto​

En el proyecto de plataforma, hay páginas que necesitan manejar grandes volúmenes de datos:

📊 Página de historial de transacciones
├─ Tabla de depositos: 1000+ registros
├─ Tabla de retiros: 800+ registros
├─ Tabla de apuestas: 5000+ registros
└─ Cada registro tiene 8-10 campos (fecha, monto, estado, etc.)

❌ Problemas sin optimizar
├─ Nodos DOM: 1000 registros × 10 campos = 10,000+ nodos
├─ Uso de memoria: ~150-200 MB
├─ Tiempo de primer renderizado: 3-5 segundos (pantalla blanca)
├─ Scroll con lag: FPS < 20
└─ Al actualizar por WebSocket: Toda la tabla se re-renderiza (muy lento)

Solución (Action)​

Virtual Scrolling​

Primero consideremos la optimización con Virtual Scroll. Hay dos enfoques: usar el paquete de terceros con soporte oficial vue-virtual-scroller, o implementarlo manualmente. Considerando el costo de desarrollo y los escenarios a cubrir, me inclinaría por adoptar el paquete con soporte oficial.

// Solo renderizar las filas visibles, por ejemplo:
// - De 100 registros, solo renderizar los 20 visibles
// - Reducción significativa de nodos DOM

Control de frecuencia de actualización de datos​

Solución 1: requestAnimationFrame (RAF) Concepto: El navegador repinta máximo 60 veces por segundo (60 FPS); actualizar más rápido es invisible para el ojo humano, así que nos sincronizamos con la tasa de refresco de la pantalla

// ❌ Original: Actualizar inmediatamente al recibir datos (100 veces/segundo posible)
socket.on('price', (newPrice) => {
btcPrice.value = newPrice;
});

// ✅ Mejorado: Recopilar datos y actualizar sincronizado con el refresco de pantalla (máximo 60/segundo)
let latestPrice = null;
let isScheduled = false;

socket.on('price', (newPrice) => {
latestPrice = newPrice;

if (!isScheduled) {
isScheduled = true;
requestAnimationFrame(() => {
btcPrice.value = latestPrice;
isScheduled = false;
});
}
});

Solución 2: Throttle Concepto: Limitar forzosamente la frecuencia de actualización, por ejemplo "máximo 1 actualización cada 100ms"

import { throttle } from 'lodash-es';

const updatePrice = throttle((newPrice) => {
btcPrice.value = newPrice;
}, 100);

socket.on('price', updatePrice);

Optimizaciones específicas de Vue3​

Algunas utilidades de Vue3 proporcionan optimizaciones de rendimiento, como v-memo, aunque personalmente lo uso raramente.

// 1. v-memo - Memoizar columnas que no cambian frecuentemente
<tr v-for="row in data"
:key="row.id"
v-memo="[row.price, row.volume]"> // Solo re-renderizar cuando estos campos cambien
</tr>

// 2. Congelar datos estáticos, evitar overhead de reactividad
const staticData = Object.freeze(largeDataArray)

// 3. shallowRef para arrays grandes
const tableData = shallowRef([...]) // Solo rastrear el array, no los objetos internos

// 4. Usar key para optimizar el algoritmo diff
<tr v-for="row in data" :key="row.id"> // key estable

Optimización de renderizado DOM​

// Usar CSS transform en vez de top/left
.row-update {
transform: translateY(0); /* Activa aceleracion GPU */
will-change: transform; /* Pista al navegador para optimizar */
}

// CSS containment para aislar el alcance del renderizado
.table-container {
contain: layout style paint;
}

Resultados de la optimización (Result)​

Comparativa de rendimiento​

IndicadorAntesDespuésMejora
Nodos DOM10,000+20-30↓ 99.7%
Memoria150-200 MB30-40 MB↓ 80%
Primer renderizado3-5s0.3-0.5s↑ 90%
FPS scroll< 2055-60↑ 200%
Respuesta de actualización500-800 ms16-33 ms↑ 95%

Puntos clave para entrevistas​

Preguntas de extensión frecuentes​

P: Si no puedes usar bibliotecas de terceros? R: Implementar la lógica central de Virtual Scroll manualmente:

const itemHeight = 50;
const containerHeight = 600;
const visibleCount = Math.ceil(containerHeight / itemHeight);

const scrollTop = container.scrollTop;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = startIndex + visibleCount;

const visibleItems = allItems.slice(startIndex, endIndex);

const paddingTop = startIndex * itemHeight;
const paddingBottom = (allItems.length - endIndex) * itemHeight;

P: Cuáles son las desventajas de Virtual Scroll? R: Trade-offs a considerar:

❌ Desventajas
├─ No se puede usar búsqueda nativa del navegador (Ctrl+F)
├─ No se puede usar "seleccionar todo" (requiere tratamiento especial)
├─ Alta complejidad de implementación
├─ Requiere altura fija o cálculo previo de altura
└─ Accesibilidad requiere tratamiento adicional

✅ Escenarios adecuados
├─ Volumen de datos > 100 registros
├─ Estructura de datos similar (altura fija)
├─ Necesidad de scroll de alto rendimiento
└─ Principalmente consulta (no edicion)

❌ Escenarios no adecuados
├─ Volumen de datos < 50 (sobrediseno)
├─ Altura variable (difícil implementación)
├─ Mucha interaccion (selección multiple, arrastrar)
└─ Necesidad de imprimir tabla completa

P: Cómo optimizar listas con alturas desiguales? R: Usar Virtual Scroll con altura dinámica:

// Opcion 1: Altura estimada + medicion real
const estimatedHeight = 50;
const measuredHeights = {};

onMounted(() => {
const elements = document.querySelectorAll('.list-item');
elements.forEach((el, index) => {
measuredHeights[index] = el.offsetHeight;
});
});

// Opcion 2: Usar paquete que soporte altura dinámica
<DynamicScroller
:items="items"
:min-item-size="50"
:buffer="200"
/>

Comparativa técnica​

Virtual Scroll vs Paginación​

AspectoVirtual ScrollPaginación tradicional
ExperienciaScroll continuo (mejor)Requiere cambiar página (interrupcion)
RendimientoSolo renderiza area visibleRenderiza toda la página
DificultadMás complejoSimple
SEOMenos favorableMás favorable
AccesibilidadTratamiento especialSoporte nativo

Recomendaciones:

  • Sistemas back-office, Dashboard → Virtual Scroll
  • Sitios web públicos, blogs → Paginación tradicional
  • Solución híbrida: Virtual Scroll + boton "Cargar más"