ListView JavaScript de Alta Performance em 2026: Windowing, Virtualizacao e Otimizacao
Domine as tecnicas de virtualizacao e windowing para criar listas JavaScript ultraperformantes. Guia completo com React e NestJS.

A exibicao de grandes volumes de dados em aplicacoes web representa um dos desafios mais significativos para desenvolvedores JavaScript. Seja lidando com milhares de produtos em um catalogo de e-commerce ou milhoes de registros em um painel analitico, uma abordagem ingenuagenuina de renderizacao inevitavelmente gera problemas de performance. A virtualizacao e o windowing constituem as solucoes modernas para resolver essas problematicas.
A virtualizacao de listas pode reduzir a quantidade de elementos DOM de varios milhares para apenas algumas dezenas, melhorando drasticamente os tempos de renderizacao e o consumo de memoria.
Compreendendo o Problema de Performance em Listas
Quando uma aplicacao JavaScript renderiza uma lista de 10.000 elementos, cada elemento gera nos DOM. O navegador precisa calcular estilos, realizar o layout e pintar cada elemento. Essa operacao consome memoria e tempo de CPU, provocando travamentos e uma experiencia de usuario degradada.
O problema se agrava com a complexidade dos elementos. Um texto simples em um <li> tem impacto limitado, mas um card de produto com imagem, descricao e botoes multiplica os custos de renderizacao.
// Abordagem ingenuagenuina - Evitar com listas grandes
interface Product {
id: string;
name: string;
price: number;
imageUrl: string;
}
function ProductList({ products }: { products: Product[] }) {
return (
<ul>
{products.map((product) => (
<li key={product.id}>
<ProductCard product={product} />
</li>
))}
</ul>
);
}O Principio do Windowing
O windowing, ou janelamento, consiste em renderizar apenas os elementos visiveis na zona de visualizacao (viewport) mais um buffer de alguns elementos acima e abaixo. Os elementos fora da tela sao destruidos e recriados dinamicamente durante a rolagem.
Essa tecnica reduz o numero de nos DOM ativos em varias ordens de grandeza. Uma lista de 100.000 elementos pode funcionar com apenas 20 a 50 nos DOM visiveis.
// Configuracao basica para windowing
interface WindowConfig {
itemHeight: number;
containerHeight: number;
overscan: number; // Elementos adicionais renderizados fora da tela
}
function calculateVisibleRange(
scrollTop: number,
config: WindowConfig,
totalItems: number
): { startIndex: number; endIndex: number } {
const { itemHeight, containerHeight, overscan } = 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
);
return { startIndex, endIndex };
}Implementacao com React Window
React Window representa a biblioteca de referencia para virtualizacao de listas em React. Leve e performante, ela gerencia automaticamente o calculo de posicoes e a reciclagem de elementos.
import { FixedSizeList as List } from 'react-window';
import { memo, useCallback } from 'react';
interface RowProps {
index: number;
style: React.CSSProperties;
data: Product[];
}
const Row = memo(function Row({ index, style, data }: RowProps) {
const product = data[index];
return (
<div style={style} className="product-row">
<img
src={product.imageUrl}
alt={product.name}
loading="lazy"
/>
<span>{product.name}</span>
<span>R$ {product.price.toFixed(2)}</span>
</div>
);
});
function VirtualizedProductList({ products }: { products: Product[] }) {
const itemData = products;
return (
<List
height={600}
itemCount={products.length}
itemSize={80}
width="100%"
itemData={itemData}
>
{Row}
</List>
);
}Listas com Altura Variavel
Listas reais frequentemente contem elementos de alturas diferentes. React Window oferece o componente VariableSizeList para lidar com essa situacao.
import { VariableSizeList as List } from 'react-window';
import { useRef, useCallback } from 'react';
interface Message {
id: string;
content: string;
author: string;
}
function ChatMessageList({ messages }: { messages: Message[] }) {
const listRef = useRef<List>(null);
// Cache de alturas calculadas
const heightCache = useRef<Map<number, number>>(new Map());
const getItemHeight = useCallback((index: number): number => {
if (heightCache.current.has(index)) {
return heightCache.current.get(index)!;
}
// Estimativa baseada no comprimento do conteudo
const message = messages[index];
const baseHeight = 60;
const charsPerLine = 50;
const lineHeight = 24;
const extraLines = Math.ceil(message.content.length / charsPerLine);
const height = baseHeight + (extraLines - 1) * lineHeight;
heightCache.current.set(index, height);
return height;
}, [messages]);
const handleContentResize = useCallback((index: number, height: number) => {
heightCache.current.set(index, height);
listRef.current?.resetAfterIndex(index);
}, []);
return (
<List
ref={listRef}
height={500}
itemCount={messages.length}
itemSize={getItemHeight}
width="100%"
>
{({ index, style }) => (
<MessageRow
message={messages[index]}
style={style}
onResize={(height) => handleContentResize(index, height)}
/>
)}
</List>
);
}Backend NestJS para Carregamento Paginado
A virtualizacao do lado do cliente funciona de forma otima com um backend que suporte paginacao. NestJS permite implementar facilmente endpoints paginados de alta performance.
import { Controller, Get, Query } from '@nestjs/common';
import { ProductService } from './product.service';
interface PaginationQuery {
offset: number;
limit: number;
search?: string;
}
interface PaginatedResponse<T> {
data: T[];
total: number;
offset: number;
limit: number;
}
@Controller('products')
export class ProductController {
constructor(private readonly productService: ProductService) {}
@Get()
async getProducts(
@Query('offset') offset = 0,
@Query('limit') limit = 50,
@Query('search') search?: string
): Promise<PaginatedResponse<Product>> {
const [data, total] = await Promise.all([
this.productService.findMany({
skip: Number(offset),
take: Math.min(Number(limit), 100), // Limite maximo
where: search
? { name: { contains: search, mode: 'insensitive' } }
: undefined,
}),
this.productService.count({
where: search
? { name: { contains: search, mode: 'insensitive' } }
: undefined,
}),
]);
return {
data,
total,
offset: Number(offset),
limit: Number(limit),
};
}
}Scroll Infinito com Virtualizacao
O scroll infinito combina a virtualizacao com o carregamento progressivo de dados. Essa abordagem oferece a melhor experiencia de usuario para colecoes grandes.
import { FixedSizeList as List } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import { useInfiniteQuery } from '@tanstack/react-query';
function InfiniteProductList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['products'],
queryFn: async ({ pageParam = 0 }) => {
const response = await fetch(
`/api/products?offset=${pageParam}&limit=50`
);
return response.json();
},
getNextPageParam: (lastPage) => {
const nextOffset = lastPage.offset + lastPage.limit;
return nextOffset < lastPage.total ? nextOffset : undefined;
},
initialPageParam: 0,
});
const allProducts = data?.pages.flatMap((page) => page.data) ?? [];
const totalCount = data?.pages[0]?.total ?? 0;
const isItemLoaded = (index: number) => {
return !hasNextPage || index < allProducts.length;
};
const loadMoreItems = isFetchingNextPage
? () => Promise.resolve()
: () => fetchNextPage();
return (
<InfiniteLoader
isItemLoaded={isItemLoaded}
itemCount={hasNextPage ? allProducts.length + 1 : allProducts.length}
loadMoreItems={loadMoreItems}
>
{({ onItemsRendered, ref }) => (
<List
ref={ref}
height={600}
itemCount={totalCount}
itemSize={80}
width="100%"
onItemsRendered={onItemsRendered}
>
{({ index, style }) => {
if (!isItemLoaded(index)) {
return (
<div style={style} className="loading-placeholder">
Carregando...
</div>
);
}
return (
<ProductRow
product={allProducts[index]}
style={style}
/>
);
}}
</List>
)}
</InfiniteLoader>
);
}Otimizacao da Performance de Renderizacao
Varias tecnicas complementares melhoram a performance da virtualizacao.
import { memo, useMemo, useCallback, useRef } from 'react';
import { areEqual } from 'react-window';
// Memoizacao agressiva de componentes de linha
const ProductRow = memo(function ProductRow({
product,
style,
onSelect,
}: {
product: Product;
style: React.CSSProperties;
onSelect: (id: string) => void;
}) {
const handleClick = useCallback(() => {
onSelect(product.id);
}, [product.id, onSelect]);
return (
<div style={style} onClick={handleClick}>
<span>{product.name}</span>
<span>R$ {product.price.toFixed(2)}</span>
</div>
);
}, areEqual);
// Hook personalizado para gerenciamento de scroll otimizado
function useOptimizedScroll(
onScroll: (scrollTop: number) => void,
delay = 16
) {
const lastScrollTop = useRef(0);
const ticking = useRef(false);
return useCallback((event: React.UIEvent<HTMLDivElement>) => {
const scrollTop = event.currentTarget.scrollTop;
lastScrollTop.current = scrollTop;
if (!ticking.current) {
requestAnimationFrame(() => {
onScroll(lastScrollTop.current);
ticking.current = false;
});
ticking.current = true;
}
}, [onScroll]);
}Grids Virtualizados
Grids bidimensionais requerem uma abordagem de virtualizacao bidirecional. React Window fornece o componente FixedSizeGrid para esse caso de uso.
import { FixedSizeGrid as Grid } from 'react-window';
interface GridItem {
id: string;
thumbnailUrl: string;
title: string;
}
function VirtualizedImageGrid({ items }: { items: GridItem[] }) {
const columnCount = 4;
const rowCount = Math.ceil(items.length / columnCount);
const Cell = useCallback(
({
columnIndex,
rowIndex,
style,
}: {
columnIndex: number;
rowIndex: number;
style: React.CSSProperties;
}) => {
const index = rowIndex * columnCount + columnIndex;
if (index >= items.length) {
return null;
}
const item = items[index];
return (
<div style={style} className="grid-cell">
<img
src={item.thumbnailUrl}
alt={item.title}
loading="lazy"
/>
<span>{item.title}</span>
</div>
);
},
[items, columnCount]
);
return (
<Grid
columnCount={columnCount}
columnWidth={200}
height={600}
rowCount={rowCount}
rowHeight={250}
width={800}
>
{Cell}
</Grid>
);
}Medicao de Performance
A medicao precisa da performance permite validar a eficacia da virtualizacao e identificar gargalos.
import { useEffect, useRef } from 'react';
function useRenderMetrics(componentName: string) {
const renderCount = useRef(0);
const lastRenderTime = useRef(performance.now());
useEffect(() => {
const currentTime = performance.now();
const timeSinceLastRender = currentTime - lastRenderTime.current;
renderCount.current++;
if (process.env.NODE_ENV === 'development') {
console.log(
`[${componentName}] Render #${renderCount.current}`,
`Time since last: ${timeSinceLastRender.toFixed(2)}ms`
);
}
lastRenderTime.current = currentTime;
});
}
// Servico NestJS para monitoramento de performance
import { Injectable } from '@nestjs/common';
@Injectable()
export class PerformanceService {
private metrics: Map<string, number[]> = new Map();
recordQueryTime(queryName: string, duration: number): void {
if (!this.metrics.has(queryName)) {
this.metrics.set(queryName, []);
}
const times = this.metrics.get(queryName)!;
times.push(duration);
// Manter apenas as ultimas 100 medicoes
if (times.length > 100) {
times.shift();
}
}
getAverageQueryTime(queryName: string): number {
const times = this.metrics.get(queryName);
if (!times || times.length === 0) return 0;
return times.reduce((sum, t) => sum + t, 0) / times.length;
}
}Pronto para mandar bem nas entrevistas de Node.js / NestJS?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
Conclusao
A virtualizacao de listas representa uma tecnica fundamental para aplicacoes JavaScript modernas que manipulam grandes volumes de dados. Combinando o windowing do lado do cliente com uma paginacao eficiente do lado do servidor, desenvolvedores podem criar interfaces fluidas capazes de exibir milhoes de elementos sem comprometer a performance.
Ferramentas como React Window simplificam consideravelmente a implementacao desses padroes. A integracao com TanStack Query para scroll infinito e NestJS para o backend cria uma arquitetura completa e de alta performance. A chave do sucesso reside na medicao continua da performance e na otimizacao iterativa baseada em dados reais.
Você saberia encontrar o bug em Node.js / NestJS?
Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Escrito por
Anthony Fillion-MailletFundador da SharpSkill
Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.
Atualizado em 15 de setembro de 2026
Compartilhar
Artigos relacionados

NestJS e Redis em 2026: Cache, Sessões e Perguntas de Entrevista
Guia completo sobre a integração do Redis com NestJS: configuração do cache-manager, gerenciamento de sessões distribuídas e perguntas técnicas para entrevistas de desenvolvedor.

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.

NestJS e MongoDB em 2026: Mongoose, Agregações e Perguntas de Entrevista
Guia completo sobre a integração do NestJS 12 com MongoDB através do Mongoose 9. Aprenda sobre schemas, pipelines de agregação e perguntas técnicas frequentes em entrevistas.