blazor-componentes-virtualizados-virtualize

Componentes virtualizados en Blazor con Virtualize

  • 5 min

Un componente virtualizado es una lista que solo renderiza los elementos visibles, en lugar de pintar todos los datos de golpe en el navegador.

Esto es muy útil cuando tenemos listas largas, tablas grandes o resultados paginados. Porque una cosa es tener 20 filas, y otra muy distinta meter 50.000 elementos en el DOM y esperar que el navegador sonría educadamente.

Blazor incluye el componente Virtualize, que se encarga de calcular qué elementos se ven en pantalla y renderizar solo esa parte. El resto de elementos existen en los datos, pero no se convierten en HTML hasta que hacen falta.

El problema de las listas grandes

La forma más directa de mostrar una lista en Blazor es usar un foreach.

@foreach (var producto in productos)
{
    <div class="producto">
        <h3>@producto.Nombre</h3>
        <p>@producto.Precio</p>
    </div>
}
Copied!

Para una lista pequeña, esto está perfecto. Simple, claro y fácil de leer.

El problema aparece cuando productos tiene miles de elementos. Blazor tiene que crear todos los nodos HTML, el navegador tiene que colocarlos, calcular estilos, medir posiciones, gestionar eventos… vamos, una fiesta.

Y encima el usuario solo ve 10 o 20 elementos a la vez.

Usando Virtualize

Para virtualizar una lista, sustituimos el foreach por el componente Virtualize.

<Virtualize Items="@productos" Context="producto">
    <div class="producto">
        <h3>@producto.Nombre</h3>
        <p>@producto.Precio</p>
    </div>
</Virtualize>
Copied!

La idea es la misma, pero ahora Blazor solo pinta los elementos necesarios para cubrir la zona visible.

El parámetro Items recibe la colección completa y Context nos da el nombre de la variable que usaremos dentro de la plantilla.

Virtualize no hace que cargar datos sea gratis. Hace que renderizar la interfaz sea mucho más barato cuando tenemos muchos elementos.

Altura del contenedor

La virtualización necesita identificar un contenedor desplazable. Podemos darle una altura limitada y activar el desplazamiento vertical:

<div style="height: 500px; overflow-y: auto;">
    <Virtualize Items="@productos" Context="producto">
        <div class="producto">
            @producto.Nombre
        </div>
    </Virtualize>
</div>
Copied!

En una aplicación real moveríamos ese estilo a una clase CSS:

.lista-productos {
    height: 500px;
    overflow-y: auto;
}
Copied!

Tamaño de los elementos

Virtualize necesita estimar cuánto ocupa cada fila para calcular qué elementos debe renderizar. Las filas deben tener la misma altura; si su tamaño cambia según el contenido, el cálculo del desplazamiento puede dejar huecos o saltos.

Por defecto puede medirlos, pero si sabemos la altura aproximada, podemos indicarla con ItemSize. Esto ayuda a que el primer renderizado sea más estable y preciso.

<Virtualize Items="@productos" Context="producto" ItemSize="72">
    <ProductoCard Producto="producto" />
</Virtualize>
Copied!

El valor está en píxeles. Cuanto más se parezca al tamaño real de cada elemento, más estable será el desplazamiento. También podemos ajustar OverscanCount, que controla cuántas filas adicionales se renderizan antes y después de la zona visible.

Cargar datos bajo demanda

Hasta ahora hemos pasado una lista completa con Items. Pero muchas veces los datos vienen de una API o una base de datos, y no queremos cargar 100.000 registros solo para enseñar 20.

Para eso usamos ItemsProvider.

<Virtualize ItemsProvider="CargarProductos" Context="producto">
    <ProductoCard Producto="producto" />
</Virtualize>
Copied!

Y en el código del componente:

private async ValueTask<ItemsProviderResult<Producto>> CargarProductos(
    ItemsProviderRequest request)
{
    var resultado = await ProductoService.GetPageAsync(
        startIndex: request.StartIndex,
        count: request.Count,
        cancellationToken: request.CancellationToken);

    return new ItemsProviderResult<Producto>(
        resultado.Items,
        resultado.TotalCount);
}
Copied!

Blazor nos dice desde qué índice necesita datos (StartIndex) y cuántos elementos quiere (Count). Nuestro servicio responde con ese bloque y con el total de elementos disponibles. También propagamos CancellationToken: al desplazarnos deprisa, una solicitud anterior puede dejar de ser necesaria.

Esto ya empieza a parecer una paginación, pero con una experiencia mucho más fluida para el usuario.

Placeholder y lista vacía

Cuando cargamos datos desde una API, puede haber un pequeño retraso. Para no dejar la interfaz “muerta”, podemos usar Placeholder.

<Virtualize ItemsProvider="CargarProductos" Context="producto">
    <ItemContent>
        <ProductoCard Producto="producto" />
    </ItemContent>
    <Placeholder>
        <div class="producto skeleton">Cargando...</div>
    </Placeholder>
    <EmptyContent>
        <p>No hay productos para mostrar.</p>
    </EmptyContent>
</Virtualize>
Copied!

Placeholder se muestra mientras los elementos están cargando. EmptyContent se muestra cuando no hay resultados.

Son pequeños detalles, pero marcan mucho la sensación de calidad de una interfaz.

Si cambian los filtros o el orden, podemos conservar una referencia al componente y pedirle los datos de nuevo:

<Virtualize @ref="lista" ItemsProvider="CargarProductos" Context="producto">
    <ProductoCard Producto="producto" />
</Virtualize>

@code {
    private Virtualize<Producto>? lista;

    private async Task AplicarFiltrosAsync()
    {
        await lista!.RefreshDataAsync();
    }
}
Copied!

El controlador de eventos ya provoca un nuevo renderizado. Si invocamos RefreshDataAsync desde una tarea en segundo plano, debemos llamar también a StateHasChanged.

Cuándo usar Virtualize

Virtualize viene muy bien en estos casos:

  • Listas largas de resultados.
  • Tablas con cientos o miles de filas.
  • Feeds, logs o historiales.
  • Componentes que repiten tarjetas relativamente pesadas.

No merece la pena usarlo para una lista de 10 elementos. Ahí un foreach normal es más claro y suficiente.

Usa Virtualize cuando el problema sea el renderizado de muchos elementos. Si la consulta a la base de datos tarda demasiado, revisa la consulta, los índices, la paginación o la caché.