# ListView JavaScript de Alta Performance em 2026: Virtualização e Técnicas de Otimização > Guia completo sobre a implementação de listas virtualizadas de alta performance em JavaScript. Técnicas de virtualização, otimização de renderização e melhores práticas para gerenciar milhares de elementos sem perda de desempenho. - Published: 2026-09-07 - Updated: 2026-09-07 - Author: Anthony Fillion-Maillet - Tags: javascript, performance, virtualizacao, react, nestjs - Reading time: 12 min --- O gerenciamento eficiente de grandes listas de dados representa um dos desafios mais comuns no desenvolvimento frontend moderno. Quando uma aplicação precisa exibir milhares ou até milhões de elementos, as abordagens tradicionais de renderização rapidamente atingem seus limites, provocando lentidões significativas e uma experiência de usuário degradada. A virtualização de listas constitui a solução de referência para resolver esse problema. Essa técnica consiste em renderizar apenas os elementos visíveis na área de visualização (viewport), enquanto simula a presença do conjunto completo de dados. O navegador manipula assim apenas uma fração do DOM, reduzindo drasticamente a carga de memória e o tempo de renderização. > A virtualização pode reduzir o número de elementos DOM de vários milhares para algumas dezenas, melhorando o desempenho de renderização em mais de 90% em alguns casos. ## Compreendendo os Fundamentos da Virtualização O princípio da virtualização baseia-se em um conceito simples: por que renderizar 10.000 elementos se o usuário só visualiza 20 por vez? A virtualização calcula dinamicamente quais elementos devem estar visíveis de acordo com a posição de rolagem, e então renderiza apenas esses elementos específicos. Essa abordagem requer vários cálculos essenciais: - A altura total estimada da lista completa - O índice do primeiro elemento visível - O índice do último elemento visível - O deslocamento (offset) para posicionar corretamente os elementos ```typescript interface VirtualizedListConfig { itemHeight: number; containerHeight: number; overscan: number; totalItems: number; } function calculateVisibleRange( scrollTop: number, config: VirtualizedListConfig ): { startIndex: number; endIndex: number; offsetY: number } { const { itemHeight, containerHeight, overscan, totalItems } = config; const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan); const visibleCount = Math.ceil(containerHeight / itemHeight); const endIndex = Math.min(totalItems - 1, startIndex + visibleCount + overscan * 2); const offsetY = startIndex * itemHeight; return { startIndex, endIndex, offsetY }; } ``` ## Implementação de uma Lista Virtualizada com NestJS e WebSocket No contexto de uma aplicação full-stack, a virtualização do lado do cliente frequentemente deve ser acompanhada de um gerenciamento inteligente de dados do lado do servidor. O NestJS oferece uma arquitetura robusta para implementar esse tipo de funcionalidade. ```typescript import { Injectable } from '@nestjs/common'; import { WebSocketGateway, WebSocketServer, SubscribeMessage } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; interface PaginatedRequest { startIndex: number; endIndex: number; searchQuery?: string; } @Injectable() @WebSocketGateway({ cors: true }) export class VirtualizedListGateway { @WebSocketServer() server: Server; private dataCache: Map = new Map(); @SubscribeMessage('requestItems') async handleItemRequest( client: Socket, payload: PaginatedRequest ): Promise { const { startIndex, endIndex, searchQuery } = payload; const cacheKey = `${searchQuery || 'all'}_${startIndex}_${endIndex}`; if (this.dataCache.has(cacheKey)) { client.emit('itemsResponse', { items: this.dataCache.get(cacheKey), startIndex, endIndex, cached: true }); return; } const items = await this.fetchItemsFromDatabase(startIndex, endIndex, searchQuery); this.dataCache.set(cacheKey, items); client.emit('itemsResponse', { items, startIndex, endIndex, cached: false }); } private async fetchItemsFromDatabase( start: number, end: number, query?: string ): Promise { // Implementação da consulta ao banco de dados return []; } } ``` ## Otimização da Renderização com React e useDeferredValue O React 19 introduz melhorias significativas para o gerenciamento de atualizações de prioridade. O hook `useDeferredValue` permite adiar atualizações menos críticas, evitando bloqueios da interface durante a rolagem rápida. ```typescript import React, { useDeferredValue, useMemo, useCallback, useRef, useEffect, useState } from 'react'; interface VirtualizedListProps { items: T[]; itemHeight: number; containerHeight: number; renderItem: (item: T, index: number) => React.ReactNode; overscan?: number; } function VirtualizedList({ items, itemHeight, containerHeight, renderItem, overscan = 3 }: VirtualizedListProps) { const [scrollTop, setScrollTop] = useState(0); const deferredScrollTop = useDeferredValue(scrollTop); const containerRef = useRef(null); const visibleRange = useMemo(() => { const startIndex = Math.max(0, Math.floor(deferredScrollTop / itemHeight) - overscan); const visibleCount = Math.ceil(containerHeight / itemHeight); const endIndex = Math.min(items.length - 1, startIndex + visibleCount + overscan * 2); return { startIndex, endIndex }; }, [deferredScrollTop, itemHeight, containerHeight, items.length, overscan]); const handleScroll = useCallback((e: React.UIEvent) => { setScrollTop(e.currentTarget.scrollTop); }, []); const visibleItems = useMemo(() => { return items.slice(visibleRange.startIndex, visibleRange.endIndex + 1); }, [items, visibleRange]); const totalHeight = items.length * itemHeight; const offsetY = visibleRange.startIndex * itemHeight; return (
{visibleItems.map((item, index) => renderItem(item, visibleRange.startIndex + index) )}
); } ``` ## Gerenciamento de Alturas Variáveis Listas com elementos de alturas diferentes representam um desafio adicional. Uma abordagem eficiente consiste em medir as alturas reais dos elementos e manter um cache dessas medições. ```typescript interface HeightCache { heights: Map; estimatedHeight: number; totalHeight: number; } class DynamicHeightManager { private cache: HeightCache = { heights: new Map(), estimatedHeight: 50, totalHeight: 0 }; constructor(private totalItems: number, estimatedHeight: number = 50) { this.cache.estimatedHeight = estimatedHeight; this.recalculateTotalHeight(); } setItemHeight(index: number, height: number): void { const previousHeight = this.cache.heights.get(index) || this.cache.estimatedHeight; this.cache.heights.set(index, height); this.cache.totalHeight += height - previousHeight; } getItemOffset(index: number): number { let offset = 0; for (let i = 0; i < index; i++) { offset += this.cache.heights.get(i) || this.cache.estimatedHeight; } return offset; } getItemHeight(index: number): number { return this.cache.heights.get(index) || this.cache.estimatedHeight; } getTotalHeight(): number { return this.cache.totalHeight; } findIndexAtOffset(offset: number): number { let currentOffset = 0; for (let i = 0; i < this.totalItems; i++) { const height = this.getItemHeight(i); if (currentOffset + height > offset) { return i; } currentOffset += height; } return this.totalItems - 1; } private recalculateTotalHeight(): void { this.cache.totalHeight = this.totalItems * this.cache.estimatedHeight; } } ``` ## Estratégias de Pré-carregamento e Cache Para garantir uma experiência de usuário fluida, o pré-carregamento de dados antes de serem visíveis é essencial. Essa técnica antecipa as necessidades do usuário carregando os dados com antecedência. ```typescript import { Injectable } from '@nestjs/common'; interface CacheEntry { data: T[]; timestamp: number; ttl: number; } @Injectable() export class PrefetchService { private cache: Map> = new Map(); private readonly DEFAULT_TTL = 60000; // 1 minuto async prefetchRange( fetcher: (start: number, end: number) => Promise, currentIndex: number, bufferSize: number = 50 ): Promise { const prefetchStart = Math.max(0, currentIndex - bufferSize); const prefetchEnd = currentIndex + bufferSize; const cacheKey = `${prefetchStart}_${prefetchEnd}`; if (this.isValid(cacheKey)) { return; } const data = await fetcher(prefetchStart, prefetchEnd); this.cache.set(cacheKey, { data, timestamp: Date.now(), ttl: this.DEFAULT_TTL }); } get(start: number, end: number): T[] | null { const cacheKey = `${start}_${end}`; const entry = this.cache.get(cacheKey); if (entry && this.isValid(cacheKey)) { return entry.data as T[]; } return null; } private isValid(key: string): boolean { const entry = this.cache.get(key); if (!entry) return false; return Date.now() - entry.timestamp < entry.ttl; } clearExpired(): void { const now = Date.now(); for (const [key, entry] of this.cache.entries()) { if (now - entry.timestamp >= entry.ttl) { this.cache.delete(key); } } } } ``` ## Otimização de Performance com Web Workers Cálculos intensivos relacionados à virtualização podem ser transferidos para Web Workers para liberar a thread principal e manter uma interface responsiva. ```typescript // virtualization.worker.ts self.onmessage = function(e: MessageEvent) { const { type, payload } = e.data; switch (type) { case 'CALCULATE_VISIBLE_RANGE': const result = calculateRange(payload); self.postMessage({ type: 'RANGE_CALCULATED', result }); break; case 'FILTER_ITEMS': const filtered = filterItems(payload.items, payload.query); self.postMessage({ type: 'ITEMS_FILTERED', result: filtered }); break; case 'SORT_ITEMS': const sorted = sortItems(payload.items, payload.comparator); self.postMessage({ type: 'ITEMS_SORTED', result: sorted }); break; } }; function calculateRange(payload: { scrollTop: number; itemHeight: number; containerHeight: number; totalItems: number; overscan: number; }) { const { scrollTop, itemHeight, containerHeight, totalItems, overscan } = payload; const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan); const visibleCount = Math.ceil(containerHeight / itemHeight); const endIndex = Math.min(totalItems - 1, startIndex + visibleCount + overscan * 2); return { startIndex, endIndex, offsetY: startIndex * itemHeight }; } function filterItems(items: T[], query: string): T[] { return items.filter(item => JSON.stringify(item).toLowerCase().includes(query.toLowerCase()) ); } function sortItems(items: T[], comparatorKey: keyof T): T[] { return [...items].sort((a, b) => { if (a[comparatorKey] < b[comparatorKey]) return -1; if (a[comparatorKey] > b[comparatorKey]) return 1; return 0; }); } ``` ## Integração com Bibliotecas Existentes Várias bibliotecas comprovadas facilitam a implementação da virtualização. React Virtualized e TanStack Virtual oferecem soluções robustas e bem documentadas. ```typescript import { useVirtualizer } from '@tanstack/react-virtual'; import { useRef } from 'react'; interface Item { id: string; title: string; description: string; } function TanStackVirtualList({ items }: { items: Item[] }) { const parentRef = useRef(null); const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 50, overscan: 5, }); return (
{virtualizer.getVirtualItems().map((virtualItem) => (

{items[virtualItem.index].title}

{items[virtualItem.index].description}

))}
); } ``` ## Medição e Monitoramento de Performance A medição contínua de performance permite identificar gargalos e otimizar adequadamente. A API Performance do navegador oferece ferramentas valiosas para esse monitoramento. ```typescript class PerformanceMonitor { private marks: Map = new Map(); private measurements: Map = new Map(); startMeasure(name: string): void { this.marks.set(name, performance.now()); } endMeasure(name: string): number { const start = this.marks.get(name); if (!start) return 0; const duration = performance.now() - start; if (!this.measurements.has(name)) { this.measurements.set(name, []); } this.measurements.get(name)!.push(duration); return duration; } getAverageTime(name: string): number { const times = this.measurements.get(name); if (!times || times.length === 0) return 0; return times.reduce((a, b) => a + b, 0) / times.length; } getReport(): Record { const report: Record = {}; for (const [name, times] of this.measurements.entries()) { report[name] = { average: this.getAverageTime(name), min: Math.min(...times), max: Math.max(...times), count: times.length }; } return report; } reset(): void { this.marks.clear(); this.measurements.clear(); } } ``` ## Conclusão A virtualização de listas representa uma técnica indispensável para qualquer desenvolvedor que trabalhe com grandes quantidades de dados. As abordagens apresentadas neste artigo, desde a implementação básica até o uso de Web Workers, oferecem um leque de soluções adaptadas a diferentes contextos. Os pontos-chave a lembrar incluem a importância do cálculo preciso dos elementos visíveis, o gerenciamento inteligente de cache para pré-carregamento, e o uso criterioso das funcionalidades do React 19 como `useDeferredValue`. A integração com bibliotecas comprovadas como TanStack Virtual simplifica consideravelmente a implementação enquanto garante performance otimizada. O domínio dessas técnicas de virtualização constitui uma vantagem importante para entrevistas técnicas, onde a capacidade de otimizar a performance de uma aplicação é frequentemente avaliada. Essas habilidades demonstram uma compreensão profunda dos mecanismos de renderização e do gerenciamento de memória em aplicações JavaScript modernas. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/node-nestjs/high-performance-javascript-listview-virtualization