virtualisasi komponen ASP.NET Core Razor

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Artikel ini menjelaskan cara menggunakan virtualisasi komponen di aplikasi ASP.NET Core Blazor .

Virtualization

Tingkatkan performa rendering komponen yang dirasakan menggunakan Blazor dukungan virtualisasi bawaan kerangka kerja dengan Virtualize<TItem> komponen . Virtualisasi adalah teknik untuk membatasi penyajian UI hanya pada bagian yang saat ini terlihat. Misalnya, virtualisasi sangat membantu ketika aplikasi harus merender daftar panjang item dan hanya subset item yang diperlukan untuk terlihat pada waktu tertentu.

Virtualize<TItem> Gunakan komponen saat:

  • Merender sekumpulan item data dalam perulangan.
  • Sebagian besar item tidak terlihat karena pengguliran.
  • Item yang dirender berukuran sama.

Saat pengguna menggulir ke titik arbitrer dalam Virtualize<TItem> daftar item komponen, komponen menghitung item yang terlihat untuk ditampilkan. Item yang tidak dilihat tidak dirender.

Tanpa virtualisasi, daftar umum mungkin menggunakan perulangan C# foreach untuk merender setiap item dalam daftar. Dalam contoh berikut:

  • allFlights adalah kumpulan penerbangan pesawat terbang.
  • Komponen FlightSummary menampilkan detail tentang setiap penerbangan.
  • Atribut @key direktif mempertahankan hubungan setiap FlightSummary komponen ke penerbangan yang dirender oleh penerbangan FlightId.
<div style="height:500px;overflow-y:scroll">
    @foreach (var flight in allFlights)
    {
        <FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
    }
</div>

Jika koleksi berisi ribuan penerbangan, penyajian penerbangan membutuhkan waktu lama dan pengguna mengalami jeda UI yang nyata. Sebagian besar penerbangan tidak terlihat karena jatuh di luar ketinggian <div> elemen.

Alih-alih merender seluruh daftar penerbangan sekaligus, ganti foreach perulangan dalam contoh sebelumnya dengan Virtualize<TItem> komponen:

  • Tentukan allFlights sebagai sumber item tetap ke Virtualize<TItem>.Items. Hanya penerbangan yang terlihat saat ini yang dirender oleh Virtualize<TItem> komponen.

    Jika koleksi non-generik menyediakan item, misalnya kumpulan DataRow, ikuti panduan di bagian Delegasi penyedia item untuk menyediakan item.

  • Tentukan konteks untuk setiap penerbangan dengan Context parameter . Dalam contoh berikut, flight digunakan sebagai konteks, yang menyediakan akses ke setiap anggota penerbangan.

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

Jika konteks tidak ditentukan dengan Context parameter , gunakan nilai context dalam templat konten item untuk mengakses setiap anggota penerbangan:

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

Komponen Virtualize<TItem> :

  • Menghitung jumlah item yang akan dirender berdasarkan tinggi kontainer dan ukuran item yang dirender.
  • Menghitung ulang dan merender item saat pengguna menggulir.
  • Hanya mengambil ikatan rekaman dari API eksternal yang sesuai dengan wilayah yang saat ini terlihat, termasuk overscan, saat ItemsProvider digunakan alih-alih Items (lihat bagian Delegasi penyedia item).

Konten item untuk Virtualize<TItem> komponen dapat mencakup:

  • HTML dan Razor kode biasa, seperti yang ditunjukkan contoh sebelumnya.
  • Satu atau beberapa Razor komponen.
  • Campuran HTML/Razor dan Razor komponen.

Delegasi penyedia item

Jika Anda tidak ingin memuat semua item ke dalam memori atau koleksi bukan generik ICollection<T>, Anda dapat menentukan metode delegasi penyedia item ke parameter komponen Virtualize<TItem>.ItemsProvider yang secara asinkron mengambil item yang diminta sesuai permintaan. Dalam contoh berikut, LoadEmployees metode ini menyediakan item ke Virtualize<TItem> komponen:

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

Penyedia item menerima ItemsProviderRequest, yang menentukan jumlah item yang diperlukan mulai dari indeks mulai tertentu. Penyedia item kemudian mengambil item yang diminta dari database atau layanan lain dan mengembalikannya sebagai ItemsProviderResult<TItem> bersama dengan jumlah total item. Penyedia item dapat memilih untuk mengambil item dengan setiap permintaan atau menyimpannya dalam cache sehingga tersedia dengan mudah.

Komponen Virtualize<TItem> hanya dapat menerima satu sumber item dari parameternya, jadi jangan mencoba menggunakan penyedia item secara bersamaan dan menetapkan koleksi ke Items. Jika keduanya ditetapkan, akan InvalidOperationException dilemparkan saat parameter komponen diatur pada runtime.

Contoh berikut memuat karyawan dari EmployeeService (tidak ditampilkan):

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);
}

Dalam contoh berikut, kumpulan DataRow adalah koleksi non-generik, sehingga delegasi penyedia item digunakan untuk virtualisasi:

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

@code{
    ...

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

Virtualize<TItem>.RefreshDataAsync menginstruksikan komponen untuk me-rerequest data dari ItemsProvider. Ini berguna ketika data eksternal berubah. Biasanya tidak perlu memanggil RefreshDataAsync saat menggunakan Items.

RefreshDataAsyncVirtualize<TItem> memperbarui data komponen tanpa menyebabkan rerender. Jika RefreshDataAsync dipanggil dari Blazor penanganan aktivitas atau metode siklus hidup komponen, memicu render tidak diperlukan karena render secara otomatis dipicu di akhir metode penanganan aktivitas atau siklus hidup. Jika RefreshDataAsync dipicu secara terpisah dari tugas atau peristiwa latar belakang, seperti di delegasi berikut ForecastUpdated , panggil StateHasChanged untuk memperbarui UI di akhir tugas atau peristiwa latar belakang:

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

...

private Virtualize<FetchData>? virtualizeComponent;

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

Dalam contoh sebelumnya:

Placeholder

Karena meminta item dari sumber data jarak jauh mungkin memakan waktu, Anda memiliki opsi untuk merender tempat penampung dengan konten item:

<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>

Isi kosong

EmptyContent Gunakan parameter untuk menyediakan konten ketika komponen telah dimuat dan Items kosong atau ItemsProviderResult<TItem>.TotalItemCount nol.

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 ??= new();
}

OnInitialized Ubah metode lambda untuk melihat string tampilan komponen:

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

Ukuran item

Tinggi setiap item dalam piksel dapat diatur dengan Virtualize<TItem>.ItemSize (default: 50). Contoh berikut mengubah tinggi setiap item dari default 50 piksel menjadi 25 piksel:

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

Secara default, Virtualize<TItem> komponen mengukur ukuran penyajian (tinggi) item individual setelah render awal terjadi. Gunakan ItemSize untuk memberikan ukuran item yang tepat terlebih dahulu untuk membantu kinerja render awal yang akurat dan untuk memastikan posisi gulir yang benar untuk pemuatan ulang halaman. Jika default ItemSize menyebabkan beberapa item dirender di luar tampilan yang saat ini terlihat, render ulang kedua dipicu. Untuk mempertahankan posisi gulir browser dengan benar dalam daftar virtual, render awal harus benar. Jika tidak, pengguna mungkin melihat item yang salah.

Jumlah overscan

Virtualize<TItem>.OverscanCount menentukan berapa banyak item tambahan yang dirender sebelum dan sesudah wilayah yang terlihat. Pengaturan ini membantu mengurangi frekuensi penyajian selama pengguliran. Namun, nilai yang lebih tinggi menghasilkan lebih banyak elemen yang dirender di halaman (default: 3). Contoh berikut mengubah jumlah overscan dari default tiga item menjadi empat item:

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

Status Perubahan

Saat membuat perubahan pada item yang dirender oleh Virtualize<TItem> komponen, ajakan StateHasChanged untuk memaksa evaluasi ulang dan penyajian ulang komponen. Untuk informasi lebih lanjut, lihat perenderan komponen Razor ASP.NET Core.

Dukungan gulir keyboard

Untuk memungkinkan pengguna menggulir konten virtual menggunakan keyboard mereka, pastikan bahwa elemen virtual atau kontainer gulir itu sendiri dapat difokuskan. Jika Anda gagal mengambil langkah ini, pengguliran keyboard tidak berfungsi di browser berbasis Chromium.

Misalnya, Anda dapat menggunakan tabindex atribut pada kontainer gulir:

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

Untuk mempelajari selengkapnya tentang arti nilai , , 0atau nilai lainnya, lihattabindex(dokumentasi MDN).-1tabindex

Gaya tingkat lanjut dan deteksi gulir

Komponen Virtualize<TItem> ini hanya dirancang untuk mendukung mekanisme tata letak elemen tertentu. Untuk memahami tata letak elemen mana yang bekerja dengan benar, berikut ini menjelaskan cara Virtualize mendeteksi elemen mana yang harus terlihat untuk ditampilkan di tempat yang benar.

Jika kode sumber Anda terlihat seperti berikut:

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

Pada runtime, Virtualize<TItem> komponen merender struktur DOM yang mirip dengan yang berikut ini:

<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>

Jumlah aktual baris yang dirender dan ukuran spacer bervariasi sesuai dengan gaya dan Items ukuran koleksi Anda. Namun, perhatikan bahwa ada elemen spacer div yang disuntikkan sebelum dan sesudah konten Anda. Ini melayani dua tujuan:

  • Untuk memberikan offset sebelum dan sesudah konten Anda, menyebabkan item yang saat ini terlihat muncul di lokasi yang benar dalam rentang gulir dan rentang gulir itu sendiri untuk mewakili ukuran total semua konten.
  • Untuk mendeteksi kapan pengguna menggulir di luar rentang yang terlihat saat ini, yang berarti bahwa konten yang berbeda harus dirender.

Catatan

Untuk mempelajari cara mengontrol tag elemen HTML spacer, lihat bagian Mengontrol nama tag elemen spacer nanti di artikel ini.

Elemen spacer secara internal menggunakan Intersection Observer untuk menerima pemberitahuan saat terlihat. Virtualize tergantung pada penerimaan peristiwa ini.

Virtualize bekerja dalam kondisi berikut:

  • Semua item konten yang dirender, termasuk konten tempat penampung, memiliki tinggi yang identik. Ini memungkinkan untuk menghitung konten mana yang sesuai dengan posisi gulir tertentu tanpa terlebih dahulu mengambil setiap item data dan merender data ke dalam elemen DOM.

  • Spasi dan baris konten dirender dalam satu tumpukan vertikal dengan setiap item mengisi seluruh lebar horizontal. Ini umumnya default. Dalam kasus umum dengan div elemen, Virtualize berfungsi secara default. Jika Anda menggunakan CSS untuk membuat tata letak yang lebih canggih, ingatlah persyaratan berikut:

    • Gaya kontainer gulir memerlukan display dengan salah satu nilai berikut:
      • block (default untuk div).
      • table-row-group (default untuk tbody).
      • flex dengan flex-direction diatur ke column. Pastikan bahwa turunan Virtualize<TItem> langsung komponen tidak menyusut di bawah aturan flex. Misalnya, tambahkan .mycontainer > div { flex-shrink: 0 }.
    • Gaya baris konten memerlukan display dengan salah satu nilai berikut:
      • block (default untuk div).
      • table-row (default untuk tr).
    • Jangan gunakan CSS untuk mengganggu tata letak untuk elemen spacer. Secara default, elemen spacer memiliki display nilai block, kecuali jika induknya adalah grup baris tabel, dalam hal ini mereka default ke table-row. Jangan mencoba memengaruhi lebar atau tinggi elemen spacer, termasuk dengan menyebabkannya memiliki batas atau content elemen pseudo.

Pendekatan apa pun yang menghentikan spacer dan elemen konten dirender sebagai tumpukan vertikal tunggal, atau menyebabkan item konten bervariasi tingginya, mencegah fungsi komponen yang Virtualize<TItem> benar.

Virtualisasi tingkat akar

Komponen Virtualize<TItem> ini mendukung penggunaan dokumen itu sendiri sebagai akar gulir, sebagai alternatif untuk memiliki beberapa elemen lain dengan overflow-y: scroll. Dalam contoh berikut, <html> elemen atau <body> ditata dalam komponen dengan overflow-y: scroll:

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

Komponen Virtualize<TItem> ini mendukung penggunaan dokumen itu sendiri sebagai akar gulir, sebagai alternatif untuk memiliki beberapa elemen lain dengan overflow-y: scroll. Saat menggunakan dokumen sebagai akar gulir, hindari <html> menata elemen atau <body> dengan overflow-y: scroll karena menyebabkan pengamat persimpangan memperlakukan tinggi halaman yang dapat digulir penuh sebagai wilayah yang terlihat, bukan hanya viewport jendela.

Anda dapat mereproduksi masalah ini dengan membuat daftar virtual besar (misalnya, 100.000 item) dan mencoba menggunakan dokumen sebagai akar gulir dengan html { overflow-y: scroll } di halaman gaya CSS. Meskipun mungkin bekerja dengan benar kadang-kadang, browser mencoba merender semua 100.000 item setidaknya sekali pada awal penyajian, yang dapat menyebabkan penguncian tab browser.

Untuk mengatasi masalah ini sebelum rilis .NET 7, hindari elemen gaya <html>/<body> dengan overflow-y: scroll atau adopsi pendekatan alternatif. Dalam contoh berikut, tinggi <html> elemen diatur ke hanya lebih dari 100% dari tinggi viewport:

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

Komponen Virtualize<TItem> ini mendukung penggunaan dokumen itu sendiri sebagai akar gulir, sebagai alternatif untuk memiliki beberapa elemen lain dengan overflow-y: scroll. Saat menggunakan dokumen sebagai akar gulir, hindari menata <html> elemen atau <body> dengan overflow-y: scroll karena menyebabkan tinggi halaman yang dapat digulir penuh diperlakukan sebagai wilayah yang terlihat, bukan hanya viewport jendela.

Anda dapat mereproduksi masalah ini dengan membuat daftar virtual besar (misalnya, 100.000 item) dan mencoba menggunakan dokumen sebagai akar gulir dengan html { overflow-y: scroll } di halaman gaya CSS. Meskipun mungkin bekerja dengan benar kadang-kadang, browser mencoba merender semua 100.000 item setidaknya sekali pada awal penyajian, yang dapat menyebabkan penguncian tab browser.

Untuk mengatasi masalah ini sebelum rilis .NET 7, hindari elemen gaya <html>/<body> dengan overflow-y: scroll atau adopsi pendekatan alternatif. Dalam contoh berikut, tinggi <html> elemen diatur ke hanya lebih dari 100% dari tinggi viewport:

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

Mengontrol nama tag elemen spacer

Virtualize<TItem> Jika komponen ditempatkan di dalam elemen yang memerlukan nama tag anak tertentu, SpacerElement memungkinkan Anda untuk mendapatkan atau mengatur nama tag spacer virtualisasi. Nilai defaultnya adalah div. Untuk contoh berikut, Virtualize<TItem> komponen merender di dalam elemen isi tabel (tbody), sehingga elemen turunan yang sesuai untuk baris tabel (tr) diatur sebagai spacer.

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();
}

Dalam contoh sebelumnya, akar dokumen digunakan sebagai kontainer gulir, sehingga html elemen dan body ditata dengan overflow-y: scroll. Untuk informasi selengkapnya, lihat sumber daya berikut: