Udostępnij za pośrednictwem


wirtualizacja składników ASP.NET Core Razor

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję artykułu .NET 9.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zobaczyć bieżącą wersję, zobacz wersję tego artykułu platformy .NET 9.

W tym artykule wyjaśniono, jak używać wirtualizacji składników w aplikacjach ASP.NET Core Blazor .

Wirtualizacja

Zwiększ postrzeganą wydajność renderowania składników, korzystając z wbudowanej w Blazor obsługi wirtualizacji platformy oraz składnika Virtualize<TItem>. Wirtualizacja to technika ograniczania renderowania interfejsu użytkownika tylko do części, które są obecnie widoczne. Na przykład wirtualizacja jest przydatna, gdy aplikacja musi renderować długą listę elementów, a tylko podzbiór elementów musi być widoczny w danym momencie.

Użyj składnika Virtualize<TItem> gdy:

  • Renderowanie zestawu elementów danych w pętli.
  • Większość elementów nie jest widoczna z powodu przewijania.
  • Renderowane elementy mają ten sam rozmiar.

Gdy użytkownik przewija dowolny punkt na Virtualize<TItem> liście elementów składnika, składnik oblicza widoczne elementy do pokazania. Niewidoczne elementy nie są renderowane.

Bez wirtualizacji typowa lista może używać pętli języka C# foreach do renderowania każdego elementu na liście. W poniższym przykładzie:

  • allFlights jest zbiorem lotów samolotowych.
  • Składnik FlightSummary wyświetla szczegółowe informacje o każdym locie.
  • Atrybut @key dyrektywy zachowuje relację każdego FlightSummary składnika z jego renderowanym lotem według parametrów lotu FlightId.
<div style="height:500px;overflow-y:scroll">
    @foreach (var flight in allFlights)
    {
        <FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
    }
</div>

Jeśli kolekcja zawiera tysiące lotów, renderowanie lotów trwa długo, a użytkownicy doświadczają zauważalnego opóźnienia interfejsu użytkownika. Większość lotów znajduje się poza wysokością elementu <div>, dlatego większość z nich nie jest widoczna.

Zamiast renderowania całej listy lotów jednocześnie, zastąp pętlę foreach w poprzednim przykładzie składnikiem Virtualize<TItem> :

  • Określ allFlights jako źródło elementu stałego dla Virtualize<TItem>.Items. Tylko aktualnie widoczne loty są renderowane przez składnik Virtualize<TItem>.

    Jeśli kolekcja niegeneryczna dostarcza elementy, na przykład kolekcja DataRow, postępuj zgodnie ze wskazówkami zawartymi w sekcji delegata dostarczającego elementy, aby zapewnić elementy.

  • Określ kontekst dla każdego lotu za pomocą parametru Context . W poniższym przykładzie flight jest używany jako kontekst, który zapewnia dostęp do członków każdego lotu.

<div style="height:500px;overflow-y:scroll">
    <Virtualize Items="allFlights" Context="flight">
        <FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
    </Virtualize>
</div>

Jeśli kontekst nie jest określony za pomocą parametru Context, użyj wartości context w szablonie zawartości elementu, aby uzyskać dostęp do elementów członkowskich każdego lotu:

<div style="height:500px;overflow-y:scroll">
    <Virtualize Items="allFlights">
        <FlightSummary @key="context.FlightId" Details="@context.Summary" />
    </Virtualize>
</div>

Składnik Virtualize<TItem> :

  • Oblicza liczbę elementów renderowanych na podstawie wysokości kontenera i rozmiaru renderowanych elementów.
  • Ponownie oblicza i przetwarza elementy, gdy użytkownik przewija.
  • Pobiera tylko fragment rekordów z zewnętrznego interfejsu API, który odpowiada obecnie widocznemu regionie, w tym przeskanowania, gdy ItemsProvider jest używany zamiast Items (zobacz sekcję Delegat dostawcy elementów).

Zawartość elementu dla Virtualize<TItem> składnika może obejmować:

  • Czysty HTML i Razor kod, jak pokazano w poprzednim przykładzie.
  • Jeden lub więcej Razor składników.
  • Mieszanka kodu HTML/Razor i Razor składników.

Delegat dostawcy elementów

Jeśli nie chcesz załadować wszystkich elementów do pamięci lub kolekcja nie jest ogólnym ICollection<T>, możesz określić metodę delegata dostawcy elementów jako parametr składnika Virtualize<TItem>.ItemsProvider, który asynchronicznie pobiera żądane elementy na żądanie. W poniższym przykładzie LoadEmployees metoda udostępnia elementy składnikowi Virtualize<TItem> :

<Virtualize Context="employee" ItemsProvider="LoadEmployees">
    <p>
        @employee.FirstName @employee.LastName has the 
        job title of @employee.JobTitle.
    </p>
</Virtualize>

Dostawca elementów otrzymuje ItemsProviderRequest, który określa wymaganą liczbę elementów, zaczynając od określonego indeksu początkowego. Dostawca elementów pobiera następnie żądane elementy z bazy danych lub innej usługi i zwraca je jako wartość ItemsProviderResult<TItem> wraz z liczbą wszystkich elementów. Dostawca elementów może pobrać elementy z każdym żądaniem lub zapisać je w pamięci podręcznej, aby były łatwo dostępne.

Virtualize<TItem> Składnik może akceptować tylko jedno źródło z jego parametrów, więc nie próbuj jednocześnie używać dostawcy elementów i przypisywać kolekcji do Items. Jeśli oba są przypisane, zgłaszany jest InvalidOperationException, gdy parametry składnika są ustawiane w czasie wykonywania.

Poniższy przykład ładuje pracowników z elementu EmployeeService (nie pokazano). Pole totalEmployees zazwyczaj jest przypisywane przez wywołanie metody w tej samej usłudze, na przykład EmployeesService.GetEmployeesCountAsync, w innym miejscu, na przykład podczas inicjowania składnika.

private async ValueTask<ItemsProviderResult<Employee>> LoadEmployees(
    ItemsProviderRequest request)
{
    var numEmployees = Math.Min(request.Count, totalEmployees - request.StartIndex);
    var employees = await EmployeesService.GetEmployeesAsync(request.StartIndex, 
        numEmployees, request.CancellationToken);

    return new ItemsProviderResult<Employee>(employees, totalEmployees);
}

W poniższym przykładzie kolekcja DataRow jest kolekcją niegeneryjną, więc delegat dostawcy elementów jest używany do wirtualizacji:

<Virtualize Context="row" ItemsProvider="GetRows">
    ...
</Virtualize>

@code{
    ...

    private ValueTask<ItemsProviderResult<DataRow>> GetRows(ItemsProviderRequest request) => 
        new(new ItemsProviderResult<DataRow>(
            dataTable.Rows.OfType<DataRow>().Skip(request.StartIndex).Take(request.Count),
            dataTable.Rows.Count));
}

Virtualize<TItem>.RefreshDataAsync instruuje składnik do ponownego pobierania danych z ItemsProvider. Jest to przydatne, gdy dane zewnętrzne zmieniają się. Zwykle nie ma potrzeby wywoływania RefreshDataAsync w przypadku korzystania z Items.

RefreshDataAsync Virtualize<TItem> aktualizuje dane składnika bez powodowania jego ponownego renderowania. Jeśli RefreshDataAsync jest wywoływana z Blazor procedury obsługi zdarzeń lub metody cyklu życia składnika, wyzwalanie renderowania nie jest wymagane, ponieważ renderowanie jest automatycznie wyzwalane na końcu procedury obsługi zdarzeń lub metody cyklu życia. Jeśli RefreshDataAsync jest uruchamiane oddzielnie od zadania lub zdarzenia w tle, na przykład jak w następującym delegacie ForecastUpdated, wywołaj metodę StateHasChanged na końcu tego zadania lub zdarzenia w tle, aby zaktualizować interfejs użytkownika.

<Virtualize ... @ref="virtualizeComponent">
    ...
</Virtualize>

...

private Virtualize<FetchData>? virtualizeComponent;

protected override void OnInitialized()
{
    WeatherForecastSource.ForecastUpdated += async () => 
    {
        await InvokeAsync(async () =>
        {
            await virtualizeComponent?.RefreshDataAsync();
            StateHasChanged();
        });
    });
}

W powyższym przykładzie:

  • RefreshDataAsync element jest wywoływany jako pierwszy w celu uzyskania nowych danych dla Virtualize<TItem> składnika.
  • StateHasChanged jest wywoływany w celu ponownego renderowania składnika.

Symbol zastępczy

Żądanie elementów ze zdalnego źródła danych może zająć trochę czasu, dlatego istnieje możliwość renderowania symbolu zastępczego z zawartością elementu:

  • Użyj znaku Placeholder (<Placeholder>...</Placeholder>), aby wyświetlić zawartość do momentu udostępnienia danych elementu.
  • Użyj Virtualize<TItem>.ItemContent polecenia , aby ustawić szablon elementu dla listy.
<Virtualize Context="employee" ItemsProvider="LoadEmployees">
    <ItemContent>
        <p>
            @employee.FirstName @employee.LastName has the 
            job title of @employee.JobTitle.
        </p>
    </ItemContent>
    <Placeholder>
        <p>
            Loading&hellip;
        </p>
    </Placeholder>
</Virtualize>

Pusta zawartość

Użyj parametru , EmptyContent aby podać zawartość, gdy składnik został załadowany i Items jest pusty lub ItemsProviderResult<TItem>.TotalItemCount ma wartość zero.

EmptyContent.razor:

@page "/empty-content"

<PageTitle>Empty Content</PageTitle>

<h1>Empty Content Example</h1>

<Virtualize Items="stringList">
    <ItemContent>
        <p>
            @context
        </p>
    </ItemContent>
    <EmptyContent>
        <p>
            There are no strings to display.
        </p>
    </EmptyContent>
</Virtualize>

@code {
    private List<string>? stringList;

    protected override void OnInitialized() => stringList ??= [];
}
@page "/empty-content"

<PageTitle>Empty Content</PageTitle>

<h1>Empty Content Example</h1>

<Virtualize Items="stringList">
    <ItemContent>
        <p>
            @context
        </p>
    </ItemContent>
    <EmptyContent>
        <p>
            There are no strings to display.
        </p>
    </EmptyContent>
</Virtualize>

@code {
    private List<string>? stringList;

    protected override void OnInitialized() => stringList ??= [];
}

Zmień metodę lambda OnInitialized, aby zobaczyć, jak komponent wyświetla teksty.

protected override void OnInitialized() =>
    stringList ??= [ "Here's a string!", "Here's another string!" ];

Rozmiar elementu

Wysokość każdego elementu w pikselach można ustawić za pomocą Virtualize<TItem>.ItemSize (domyślnie: 50). Poniższy przykład zmienia wysokość każdego elementu z wartości domyślnej 50 pikseli na 25 pikseli:

<Virtualize Context="employee" Items="employees" ItemSize="25">
    ...
</Virtualize>

Składnik Virtualize<TItem> mierzy rozmiar renderowania (wysokość) poszczególnych elementów po początkowym renderowaniu. Użyj ItemSize do podania z góry dokładnego rozmiaru elementu, aby ułatwić dokładne początkowe renderowanie i zapewnić poprawną pozycję przewijania podczas ponownego ładowania strony. Jeśli ustawienie domyślne ItemSize powoduje, że niektóre elementy będą renderowane poza obecnie widocznym widokiem, zostanie wyzwolony drugi rerender. Aby poprawnie zachować położenie przewijania przeglądarki na zwirtualizowanej liście, początkowy render musi być prawidłowy. Jeśli nie, użytkownicy mogą wyświetlać nieprawidłowe elementy.

Liczba przeskanów

Virtualize<TItem>.OverscanCount określa liczbę dodatkowych elementów renderowanych przed i po widocznym regionie. To ustawienie pomaga zmniejszyć częstotliwość renderowania podczas przewijania. Jednak wyższe wartości powodują więcej elementów renderowanych na stronie (wartość domyślna: 3). Poniższy przykład zmienia liczbę przeskanów z wartości domyślnej trzech elementów na cztery elementy:

<Virtualize Context="employee" Items="employees" OverscanCount="4">
    ...
</Virtualize>

Zmiany stanu

Podczas wprowadzania zmian w elementach renderowanych przez składnik Virtualize<TItem>, należy wywołać StateHasChanged, aby dodać komponent do kolejki w celu ponownej oceny i ponownego renderowania. Aby uzyskać więcej informacji, zobacz Renderowanie składników platformy ASP.NET Core Razor.

Obsługa przewijania klawiatury

Aby umożliwić użytkownikom przewijanie zwirtualizowanej zawartości przy użyciu klawiatury, upewnij się, że zwirtualizowane elementy lub sam kontener przewijania można skupić. Jeśli nie zrobisz tego kroku, przewijanie klawiatury nie działa w przeglądarkach opartych na chromium.

Można na przykład użyć atrybutu tabindex do kontenera przewijania.

<div style="height:500px; overflow-y:scroll" tabindex="-1">
    <Virtualize Items="allFlights">
        <div class="flight-info">...</div>
    </Virtualize>
</div>

Aby dowiedzieć się więcej o znaczeniu wartości tabindex, wartości -1, wartości 0 lub innych wartości, zobacz tabindex (dokumentacja MDN).

Zaawansowane style i detekcja przewijania

Składnik Virtualize<TItem> jest przeznaczony tylko do obsługi określonych mechanizmów układu elementów. Aby zrozumieć, które układy elementów działają poprawnie, w poniższym artykule wyjaśniono, jak Virtualize wykrywać, które elementy powinny być widoczne dla wyświetlania we właściwym miejscu.

Jeśli kod źródłowy wygląda następująco:

<div style="height:500px; overflow-y:scroll" tabindex="-1">
    <Virtualize Items="allFlights" ItemSize="100">
        <div class="flight-info">Flight @context.Id</div>
    </Virtualize>
</div>

W czasie wykonywania Virtualize<TItem> składnik renderuje strukturę DOM podobną do następującej:

<div style="height:500px; overflow-y:scroll" tabindex="-1">
    <div style="height:1100px"></div>
    <div class="flight-info">Flight 12</div>
    <div class="flight-info">Flight 13</div>
    <div class="flight-info">Flight 14</div>
    <div class="flight-info">Flight 15</div>
    <div class="flight-info">Flight 16</div>
    <div style="height:3400px"></div>
</div>

Rzeczywista liczba renderowanych wierszy i rozmiar odstępów różnią się w zależności od stylu i rozmiaru kolekcji Items. Należy jednak zauważyć, że istnieją elementy odstępu div wprowadzone przed i po zawartości. Służą one dwóm celom:

  • Aby zapewnić przesunięcie przed zawartością i po niej, co spowoduje, że bieżące widoczne elementy będą wyświetlane w odpowiedniej lokalizacji w zakresie przewijania, a zakres przewijania będzie reprezentować całkowity rozmiar całej zawartości.
  • Aby wykryć, kiedy użytkownik przewija się poza bieżący widoczny zakres, co oznacza, że inna zawartość musi być renderowana.

Uwaga

Aby dowiedzieć się, jak kontrolować tag elementu spacer HTML, zobacz sekcję Kontrolowanie nazwy tagu elementu spacer w dalszej części tego artykułu.

Elementy odstępu wewnętrznie używają obserwatora skrzyżowania do odbierania powiadomień, gdy staną się widoczne. Virtualize zależy od odbierania tych zdarzeń.

Virtualize działa w następujących warunkach:

  • Wszystkie renderowane elementy treści, w tym treść zastępcza, mają identyczną wysokość. Dzięki temu można obliczyć, która zawartość odpowiada danej pozycji przewijania bez uprzedniego pobierania każdego elementu danych i renderowania danych w elemencie DOM.

  • Zarówno odstępy, jak i wiersze zawartości są renderowane w jednym pionowym stosie z każdym elementem wypełniającym całą szerokość poziomą. W typowych przypadkach Virtualize działa z elementami div. Jeśli używasz arkuszy CSS do utworzenia bardziej zaawansowanego układu, pamiętaj o następujących wymaganiach:

    • Styl przewijania kontenera wymaga elementu display z dowolną z następujących wartości:
      • block (wartość domyślna dla elementu div).
      • table-row-group (wartość domyślna dla elementu tbody).
      • flex z ustawioną wartością flex-directioncolumn. Upewnij się, że bezpośrednie elementy podrzędne Virtualize<TItem> składnika nie kurczą się zgodnie z zasadami flex. Na przykład dodaj nazwę .mycontainer > div { flex-shrink: 0 }.
    • Styl wiersza zawartości wymaga elementu display z jedną z następujących wartości:
      • block (wartość domyślna dla elementu div).
      • table-row (wartość domyślna dla elementu tr).
    • Nie używaj CSS do zakłócania układu elementów odstępowych. Elementy odstępu mają display wartość block, z wyjątkiem sytuacji, gdy element nadrzędny jest grupą wierszy tabeli, przyjmuje wówczas domyślnie wartość table-row. Nie próbuj wpływać na szerokość lub wysokość elementu odstępu, w tym przez dodanie im obramowania lub content pseudoelementów.

Każde podejście, które uniemożliwia renderowanie elementów odstępu i zawartości jako pojedynczego pionowego stosu lub powoduje, że elementy zawartości różnią się wysokością, uniemożliwia prawidłowe działanie składnika Virtualize<TItem>.

Wirtualizacja na poziomie głównym

Składnik Virtualize<TItem> obsługuje używanie samego dokumentu jako głównego kontenera przewijania, jako alternatywę dla posiadania innego elementu z overflow-y: scroll. W poniższym przykładzie elementy <html> lub <body> są stylizowane w składniku z użyciem overflow-y: scroll.

<HeadContent>
    <style>
        html, body { overflow-y: scroll }
    </style>
</HeadContent>

Składnik Virtualize<TItem> obsługuje używanie samego dokumentu jako korzenia przewijania, jako alternatywę dla posiadania innego elementu z overflow-y: scroll. W przypadku używania dokumentu jako głównego korzenia przewijania należy unikać stylizowania elementów <html> lub <body> z overflow-y: scroll, ponieważ powoduje to, że obserwator przecięcia traktuje pełną wysokość strony jako widoczny obszar, zamiast tylko okna widoku.

Ten problem można odtworzyć, tworząc dużą listę zwirtualizowaną (na przykład 100 000 elementów) i próbując użyć dokumentu jako korzenia przewijania z html { overflow-y: scroll } w stylach CSS strony. Chociaż czasami może działać poprawnie, przeglądarka próbuje renderować wszystkie 100 000 elementów co najmniej raz na początku renderowania, co może spowodować zablokowanie karty przeglądarki.

Aby obejść ten problem przed wydaniem programu .NET 7, należy unikać tworzenia stylów <html>/<body> elementów za pomocą overflow-y: scroll lub przyjąć alternatywne podejście. W poniższym przykładzie wysokość <html> elementu jest ustawiona na nieco ponad 100% wysokości widoku:

<HeadContent>
    <style>
        html { min-height: calc(100vh + 0.3px) }
    </style>
</HeadContent>

Składnik Virtualize<TItem> obsługuje używanie samego dokumentu jako głównego elementu przewijania, jako alternatywę dla innego elementu z overflow-y: scroll. W przypadku użycia dokumentu jako głównego elementu przewijania należy unikać stylizowania elementów <html> lub <body> za pomocą overflow-y: scroll, ponieważ powoduje to, że cała przewijalna wysokość strony jest traktowana jako widoczny region, zamiast jedynie okna widoku.

Ten problem można odtworzyć, tworząc zwirtualizowaną listę (na przykład 100 000 elementów) i próbując użyć dokumentu jako korzenia przewijania z html { overflow-y: scroll } w stylach CSS strony. Chociaż czasami może działać poprawnie, przeglądarka próbuje renderować wszystkie 100 000 elementów co najmniej raz na początku renderowania, co może spowodować zablokowanie karty przeglądarki.

Aby obejść ten problem przed wydaniem programu .NET 7, należy unikać tworzenia stylów <html>/<body> elementów za pomocą overflow-y: scroll lub przyjąć alternatywne podejście. W poniższym przykładzie wysokość <html> elementu jest ustawiona na nieco ponad 100% wysokości widoku:

<style>
    html { min-height: calc(100vh + 0.3px) }
</style>

Kontrolowanie nazwy tagu elementu spacer

Jeśli składnik Virtualize<TItem> zostanie umieszczony wewnątrz elementu, który wymaga określonej nazwy tagu podrzędnego, SpacerElement pozwala uzyskać lub ustawić nazwę tagu odstępnika wirtualizacji. Domyślna wartość to div. W poniższym przykładzie komponent Virtualize<TItem> jest renderowany wewnątrz elementu treści tabeli (tbody), więc odpowiedni element potomny dla wiersza tabeli (tr) jest ustawiony jako odstęp.

VirtualizedTable.razor:

@page "/virtualized-table"

<PageTitle>Virtualized Table</PageTitle>

<HeadContent>
    <style>
        html, body {
            overflow-y: scroll
        }
    </style>
</HeadContent>

<h1>Virtualized Table Example</h1>

<table id="virtualized-table">
    <thead style="position: sticky; top: 0; background-color: silver">
        <tr>
            <th>Item</th>
            <th>Another column</th>
        </tr>
    </thead>
    <tbody>
        <Virtualize Items="fixedItems" ItemSize="30" SpacerElement="tr">
            <tr @key="context" style="height: 30px;" id="row-@context">
                <td>Item @context</td>
                <td>Another value</td>
            </tr>
        </Virtualize>
    </tbody>
</table>

@code {
    private List<int> fixedItems = Enumerable.Range(0, 1000).ToList();
}

W poprzednim przykładzie korzeń dokumentu jest używany jako kontener przewijania, więc elementy html i body są stylizowane za pomocą overflow-y: scroll. Aby uzyskać więcej informacji, zobacz następujące zasoby: