Bagikan melalui


praktik terbaik performa penyajian inti Blazor ASP.NET

Nota

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

Peringatan

Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi lebih lanjut, lihat Kebijakan Dukungan .NET dan .NET Core . Untuk rilis saat ini, lihat versi .NET 9 dari artikel ini.

Optimalkan kecepatan penyajian untuk meminimalkan beban kerja penyajian dan meningkatkan respons UI, yang dapat menghasilkan peningkatan sepuluh kali lipat atau lebih tinggi dalam kecepatan penyajian UI.

Hindari penyajian subtrees komponen yang tidak perlu

Anda mungkin dapat menghapus sebagian besar biaya penyajian komponen induk dengan melewati rerendering subtree komponen anak saat peristiwa terjadi. Anda hanya perlu khawatir tentang melewatkan subtree rerendering yang sangat mahal untuk dirender dan menyebabkan keterlambatan UI.

Saat runtime, komponen ada dalam hierarki. Komponen akar (komponen pertama yang dimuat) memiliki komponen anak. Pada gilirannya, anak-anak akar memiliki komponen anak mereka sendiri, dan sebagainya. Saat peristiwa terjadi, seperti pengguna yang memilih tombol, proses berikut menentukan komponen mana yang akan dirender:

  1. Peristiwa dikirim ke komponen yang merender handler peristiwa. Setelah menjalankan penanganan aktivitas, komponen dirender.
  2. Ketika komponen dirender, komponen tersebut memasok salinan nilai parameter baru ke setiap komponen turunannya.
  3. Setelah serangkaian nilai parameter baru diterima, Blazor memutuskan apakah akan merender komponen. Komponen merender kembali jika ShouldRender mengembalikan true, yang merupakan perilaku default kecuali ditimpa, dan nilai parameter mungkin telah berubah, misalnya, jika objek tersebut dapat diubah.

Dua langkah terakhir dari urutan sebelumnya berlanjut secara rekursif ke hierarki komponen. Dalam banyak kasus, seluruh subtree dirender. Peristiwa yang menargetkan komponen tingkat tinggi dapat menyebabkan rerendering mahal karena setiap komponen di bawah komponen tingkat tinggi harus dirender.

Untuk mencegah rendering rekursi ke dalam subtree tertentu, gunakan salah satu pendekatan berikut:

  • Pastikan bahwa parameter komponen anak adalah jenis tertentu yang tidak dapat diubah†, seperti string, , intbool, dan DateTime. Logika bawaan untuk mendeteksi perubahan secara otomatis menghindari penyajian ulang jika nilai parameter yang tidak dapat diubah belum berubah. Jika Anda merender komponen anak dengan <Customer CustomerId="item.CustomerId" />, di mana CustomerId adalah int jenis, maka Customer komponen tidak dirender kecuali item.CustomerId perubahan.
  • Ambil alih ShouldRender, mengembalikan false:
    • Ketika parameter adalah tipe nonprimitif atau tipe immutable yang tidak didukung, seperti tipe model kustom kompleks atau nilai RenderFragment, dan nilai parameter belum berubah,
    • Jika penulisan komponen khusus UI yang tidak berubah setelah render awal, terlepas dari perubahan nilai parameter.

†Untuk informasi selengkapnya, lihat logika deteksi perubahan di Blazorsumber referensi (ChangeDetection.cs).

Nota

Tautan dokumentasi ke sumber referensi .NET biasanya memuat cabang default repositori, yang mewakili pengembangan saat ini untuk rilis .NET berikutnya. Untuk memilih tag untuk rilis tertentu, gunakan menu dropdown Ganti cabang atau tag. Untuk informasi lebih lanjut, lihat Cara memilih tag versi kode sumber ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Contoh alat pencarian penerbangan maskapai berikut menggunakan bidang privat untuk melacak informasi yang diperlukan untuk mendeteksi perubahan. Pengidentifikasi penerbangan masuk sebelumnya (prevInboundFlightId) dan pengidentifikasi penerbangan keluar sebelumnya (prevOutboundFlightId) melacak informasi untuk pembaruan komponen potensial berikutnya. Jika salah satu pengidentifikasi penerbangan berubah ketika parameter komponen diatur dalam OnParametersSet, komponen dirender karena shouldRender diatur ke true. Jika shouldRender mengevaluasi ke false setelah memeriksa pengidentifikasi penerbangan, rerender yang mahal dihindari:

@code {
    private int prevInboundFlightId = 0;
    private int prevOutboundFlightId = 0;
    private bool shouldRender;

    [Parameter]
    public FlightInfo? InboundFlight { get; set; }

    [Parameter]
    public FlightInfo? OutboundFlight { get; set; }

    protected override void OnParametersSet()
    {
        shouldRender = InboundFlight?.FlightId != prevInboundFlightId
            || OutboundFlight?.FlightId != prevOutboundFlightId;

        prevInboundFlightId = InboundFlight?.FlightId ?? 0;
        prevOutboundFlightId = OutboundFlight?.FlightId ?? 0;
    }

    protected override bool ShouldRender() => shouldRender;
}

Penanganan aktivitas juga dapat diatur shouldRender ke true. Untuk sebagian besar komponen, menentukan rerendering pada tingkat penangan peristiwa individu biasanya tidak diperlukan.

Untuk informasi selengkapnya, lihat sumber daya berikut ini:

Virtualisasi

Saat merender UI dalam jumlah besar dalam perulangan, misalnya, daftar atau kisi dengan ribuan entri, kuantitas operasi penyajian yang besar dapat menyebabkan jeda dalam penyajian UI. Mengingat bahwa pengguna hanya dapat melihat sejumlah kecil elemen sekaligus tanpa menggulir, seringkali boros untuk menghabiskan elemen penyajian waktu yang saat ini tidak terlihat.

Blazor Virtualize<TItem> menyediakan komponen untuk membuat perilaku tampilan dan gulir dari daftar yang sangat besar sambil hanya merender item daftar yang berada dalam viewport gulir saat ini. Misalnya, komponen dapat merender daftar dengan 100.000 entri tetapi hanya membayar biaya penyajian 20 item yang terlihat.

Untuk informasi selengkapnya, lihat virtualisasi komponen ASP.NET CoreRazor.

Membuat komponen yang ringan dan dioptimalkan

Sebagian besar Razor komponen tidak memerlukan upaya pengoptimalan yang agresif karena sebagian besar komponen tidak mengulangi di UI dan tidak merender pada frekuensi tinggi. Misalnya, komponen yang dapat dirutekan dengan @page direktif dan komponen yang digunakan untuk merender potongan UI tingkat tinggi, seperti dialog atau formulir, kemungkinan besar hanya muncul satu per satu dan hanya dirender sebagai respons terhadap gerakan pengguna. Komponen-komponen ini biasanya tidak membuat beban kerja penyajian tinggi, sehingga Anda dapat dengan bebas menggunakan kombinasi fitur kerangka kerja apa pun tanpa banyak kekhawatiran tentang performa penyajian.

Namun, ada skenario umum di mana komponen diulang dalam skala besar dan sering mengakibatkan performa UI yang buruk:

  • Bentuk berlapis besar dengan ratusan elemen individual, seperti input atau label.
  • Kisi dengan ratusan baris atau ribuan sel.
  • Menyebarkan plot dengan jutaan titik data.

Jika memodelkan setiap elemen, sel, atau titik data sebagai instans komponen terpisah, seringkali ada begitu banyak dari mereka sehingga performa penyajiannya menjadi penting. Bagian ini memberikan saran tentang membuat komponen tersebut ringan sehingga UI tetap cepat dan responsif.

Hindari ribuan instans komponen

Setiap komponen adalah pulau terpisah yang dapat merender secara independen dari orang tua dan anak-anaknya. Dengan memilih cara membagi UI menjadi hierarki komponen, Anda mengambil kendali atas granularitas penyajian UI. Hal ini dapat mengakibatkan performa yang baik atau buruk.

Dengan membagi UI menjadi komponen terpisah, Anda dapat memiliki bagian yang lebih kecil dari render UI saat peristiwa terjadi. Dalam tabel dengan banyak baris yang memiliki tombol di setiap baris, Anda mungkin hanya dapat memiliki satu baris yang dirender dengan menggunakan komponen anak alih-alih seluruh halaman atau tabel. Namun, setiap komponen memerlukan memori tambahan dan overhead CPU untuk menangani status independen dan siklus hidup penyajiannya.

Dalam pengujian yang dilakukan oleh teknisi unit produk ASP.NET Core, overhead penyajian sekitar 0,06 ms per instans komponen terlihat di aplikasi Blazor WebAssembly . Aplikasi pengujian merender komponen sederhana yang menerima tiga parameter. Secara internal, overhead sebagian besar karena mengambil status per komponen dari kamus dan melewati dan menerima parameter. Dengan perkalian, Anda dapat melihat bahwa menambahkan 2.000 instans komponen tambahan akan menambahkan 0,12 detik ke waktu penyajian dan UI akan mulai merasa lambat bagi pengguna.

Dimungkinkan untuk membuat komponen lebih ringan sehingga Anda dapat memiliki lebih banyak dari mereka. Namun, teknik yang lebih kuat sering kali untuk menghindari memiliki begitu banyak komponen untuk dirender. Bagian berikut ini menjelaskan dua pendekatan yang dapat Anda ambil.

Untuk informasi selengkapnya tentang manajemen memori, lihat Mengelola memori di aplikasi sisi Blazor server ASP.NET Core yang disebarkan.

Menjadikan komponen anak sebaris dengan induknya: Pertimbangkan bagian berikut dari komponen induk yang merender komponen anak secara berulang dalam sebuah perulangan:

<div class="chat">
    @foreach (var message in messages)
    {
        <ChatMessageDisplay Message="message" />
    }
</div>

ChatMessageDisplay.razor:

<div class="chat-message">
    <span class="author">@Message.Author</span>
    <span class="text">@Message.Text</span>
</div>

@code {
    [Parameter]
    public ChatMessage? Message { get; set; }
}

Contoh sebelumnya berkinerja baik jika ribuan pesan tidak ditampilkan sekaligus. Untuk menampilkan ribuan pesan sekaligus, pertimbangkan untuk tidak memperhitungkan Sebagai gantinya, sebariskan komponen anak ke dalam induk. Pendekatan berikut menghindari overhead per komponen dari penyajian begitu banyak komponen anak dengan biaya kehilangan kemampuan untuk merender markup setiap komponen anak secara independen:

<div class="chat">
    @foreach (var message in messages)
    {
        <div class="chat-message">
            <span class="author">@message.Author</span>
            <span class="text">@message.Text</span>
        </div>
    }
</div>

RenderFragments Anda mungkin memisahkan subkomponen sebagai cara menggunakan kembali logika pemrosesan tampilan. Jika demikian, Anda dapat membuat logika penyajian yang dapat digunakan kembali tanpa menerapkan komponen tambahan. Dalam blok komponen @code apa pun, tentukan RenderFragment. Render fragmen dari lokasi mana pun sebanyak yang diperlukan:

@RenderWelcomeInfo

<p>Render the welcome content a second time:</p>

@RenderWelcomeInfo

@code {
    private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!</p>;
}

Untuk membuat RenderTreeBuilder kode dapat digunakan kembali di beberapa komponen, nyatakan RenderFragmentpublic dan static:

public static RenderFragment SayHello = @<h1>Hello!</h1>;

SayHello dalam contoh sebelumnya dapat dipanggil dari komponen yang tidak terkait. Teknik ini berguna untuk membangun pustaka cuplikan markup yang dapat digunakan kembali yang dirender tanpa overhead per komponen.

RenderFragment delegasi dapat menerima parameter. Komponen berikut meneruskan pesan (message) ke RenderFragment delegasi:

<div class="chat">
    @foreach (var message in messages)
    {
        @ChatMessageDisplay(message)
    }
</div>

@code {
    private RenderFragment<ChatMessage> ChatMessageDisplay = message =>
        @<div class="chat-message">
            <span class="author">@message.Author</span>
            <span class="text">@message.Text</span>
        </div>;
}

Pendekatan sebelumnya menggunakan kembali logika penyajian tanpa overhead per komponen. Namun, pendekatan tidak mengizinkan refresh subtree UI secara independen, juga tidak memiliki kemampuan untuk melewati penyajian subtree UI ketika induknya merender karena tidak ada batas komponen. Penugasan ke delegasi RenderFragment hanya didukung pada file komponen Razor (.razor).

Untuk bidang, metode, atau properti non-statis yang tidak dapat dirujuk oleh penginisialisasi bidang, seperti TitleTemplate dalam contoh berikut, gunakan properti alih-alih bidang untuk RenderFragment:

protected RenderFragment DisplayTitle =>
    @<div>
        @TitleTemplate
    </div>;

Jangan menerima terlalu banyak parameter

Jika komponen berulang sangat sering, misalnya, ratusan atau ribuan kali, overhead meneruskan dan menerima setiap parameter dibangun.

Jarang bahwa terlalu banyak parameter sangat membatasi performa, tetapi dapat menjadi faktor. TableCell Untuk komponen yang merender 4.000 kali dalam kisi, setiap parameter yang diteruskan ke komponen menambahkan sekitar 15 md ke total biaya penyajian. Melewati sepuluh parameter membutuhkan sekitar 150 md dan menyebabkan jeda penyajian UI.

Untuk mengurangi beban parameter, bundelkan beberapa parameter di kelas kustom. Misalnya, komponen sel tabel mungkin menerima objek umum. Dalam contoh berikut, Data berbeda untuk setiap sel, tetapi Options umum di semua instans sel:

@typeparam TItem

...

@code {
    [Parameter]
    public TItem? Data { get; set; }
    
    [Parameter]
    public GridOptions? Options { get; set; }
}

Namun, perlu diingat bahwa menggabungkan parameter primitif ke dalam kelas tidak selalu menjadi keuntungan. Meskipun dapat mengurangi jumlah parameter, itu juga berdampak pada bagaimana deteksi perubahan dan perilaku penyajian. Pengoperan parameter non-primitif selalu memicu render ulang, karena Blazor tidak dapat mengetahui apakah objek arbitrer memiliki status yang dapat diubah secara internal, sedangkan pengoperan parameter primitif hanya memicu render ulang jika nilainya benar-benar berubah.

Selain itu, pertimbangkan bahwa mungkin merupakan peningkatan untuk tidak memiliki komponen sel tabel, seperti yang ditunjukkan dalam contoh sebelumnya, dan sebaliknya sebaris logikanya ke komponen induk.

Nota

Ketika beberapa pendekatan tersedia untuk meningkatkan performa, tolok ukur pendekatan biasanya diperlukan untuk menentukan pendekatan mana yang menghasilkan hasil terbaik.

Untuk informasi selengkapnya tentang parameter jenis generik (@typeparam), lihat sumber daya berikut:

Pastikan parameter berskala diperbaiki

Komponen CascadingValue memiliki parameter opsionalIsFixed:

  • Jika IsFixed adalah false (default), setiap penerima nilai bertingkat menyiapkan langganan untuk menerima pemberitahuan perubahan. Masing-masing [CascadingParameter] secara substansial lebih mahal
  • Jika IsFixed adalah true (misalnya, <CascadingValue Value="someValue" IsFixed="true">), penerima menerima nilai awal tetapi tidak menyiapkan langganan untuk menerima pembaruan. Masing-masing [CascadingParameter] ringan dan tidak lebih mahal daripada biasa [Parameter].

Pengaturan IsFixed untuk true meningkatkan performa jika ada sejumlah besar komponen lain yang menerima nilai bertingkat. Jika memungkinkan, atur IsFixed ke true pada nilai bertingkat. Anda dapat mengatur IsFixed ke saat nilai yang disediakan tidak berubah dari waktu ke true waktu.

Ketika sebuah komponen mengoper this sebagai nilai cascaded, IsFixed juga dapat diatur ke true, karena this tidak pernah berubah selama siklus hidup komponen.

<CascadingValue Value="this" IsFixed="true">
    <SomeOtherComponents>
</CascadingValue>

Untuk informasi selengkapnya, lihat ASP.NET Core Blazor nilai dan parameter berjenjang.

Hindari percikan atribut dengan CaptureUnmatchedValues

Komponen dapat memilih untuk menerima nilai parameter "tidak cocok" menggunakan CaptureUnmatchedValues bendera:

<div @attributes="OtherAttributes">...</div>

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public IDictionary<string, object>? OtherAttributes { get; set; }
}

Pendekatan ini memungkinkan meneruskan atribut tambahan arbitrer ke elemen . Namun, pendekatan ini mahal karena perender harus:

  • Cocokkan semua parameter yang disediakan dengan sekumpulan parameter yang diketahui untuk membangun kamus.
  • Lacak bagaimana beberapa salinan atribut yang sama saling menimpa.

Gunakan CaptureUnmatchedValues di mana performa penyajian komponen tidak penting, seperti komponen yang tidak sering diulang. Untuk komponen yang dirender dalam skala besar, seperti setiap item dalam daftar besar atau di sel kisi, coba hindari percikan atribut.

Untuk informasi selengkapnya, lihat ASP.NET Core Blazor mengenai splatting atribut dan parameter sembarang.

Menerapkan SetParametersAsync secara manual

Sumber overhead penyajian per komponen yang signifikan menulis nilai parameter masuk ke [Parameter] properti. Perender menggunakan refleksi untuk menulis nilai parameter, yang dapat menyebabkan performa yang buruk dalam skala besar.

Dalam beberapa kasus ekstrem, Anda mungkin ingin menghindari pantulan dan menerapkan logika pengaturan parameter Anda sendiri secara manual. Ini mungkin berlaku ketika:

  • Komponen sangat sering dirender, misalnya, ketika ada ratusan atau ribuan salinan komponen di UI.
  • Komponen menerima banyak parameter.
  • Anda menemukan bahwa overhead parameter penerimaan memiliki dampak yang dapat diamati pada responsI UI.

Dalam kasus ekstrem, Anda dapat mengambil alih metode virtual SetParametersAsync komponen dan menerapkan logika khusus komponen Anda sendiri. Contoh berikut sengaja menghindari pencarian kamus:

@code {
    [Parameter]
    public int MessageId { get; set; }

    [Parameter]
    public string? Text { get; set; }

    [Parameter]
    public EventCallback<string> TextChanged { get; set; }

    [Parameter]
    public Theme CurrentTheme { get; set; }

    public override Task SetParametersAsync(ParameterView parameters)
    {
        foreach (var parameter in parameters)
        {
            switch (parameter.Name)
            {
                case nameof(MessageId):
                    MessageId = (int)parameter.Value;
                    break;
                case nameof(Text):
                    Text = (string)parameter.Value;
                    break;
                case nameof(TextChanged):
                    TextChanged = (EventCallback<string>)parameter.Value;
                    break;
                case nameof(CurrentTheme):
                    CurrentTheme = (Theme)parameter.Value;
                    break;
                default:
                    throw new ArgumentException($"Unknown parameter: {parameter.Name}");
            }
        }

        return base.SetParametersAsync(ParameterView.Empty);
    }
}

Dalam kode sebelumnya, mengembalikan kelas SetParametersAsync dasar menjalankan metode siklus hidup normal tanpa menetapkan parameter lagi.

Seperti yang Anda lihat dalam kode sebelumnya, mengambil alih dan menyediakan logika kustom rumit dan melelahkan, jadi kami umumnya tidak merekomendasikan untuk mengadopsi SetParametersAsync pendekatan ini. Dalam kasus ekstrem, ini dapat meningkatkan performa penyajian sebesar 20-25%, tetapi Anda hanya boleh mempertimbangkan pendekatan ini dalam skenario ekstrem yang tercantum sebelumnya di bagian ini.

Jangan memicu peristiwa terlalu cepat

Beberapa peristiwa browser sering terjadi. Misalnya, onmousemove dan onscroll dapat menembakkan puluhan atau ratusan kali per detik. Dalam kebanyakan kasus, Anda tidak perlu sering melakukan pembaruan UI. Jika peristiwa dipicu terlalu cepat, Anda dapat membahayakan respons UI atau mengonsumsi waktu CPU yang berlebihan.

Daripada menggunakan peristiwa asli yang dengan cepat menembak, pertimbangkan penggunaan JS interop untuk mendaftarkan panggilan balik yang lebih jarang diaktifkan. Misalnya, komponen berikut menampilkan posisi mouse tetapi hanya diperbarui paling banyak sekali setiap 500 mdtk:

@implements IDisposable
@inject IJSRuntime JS

<h1>@message</h1>

<div @ref="mouseMoveElement" style="border:1px dashed red;height:200px;">
    Move mouse here
</div>

@code {
    private ElementReference mouseMoveElement;
    private DotNetObjectReference<MyComponent>? selfReference;
    private string message = "Move the mouse in the box";

    [JSInvokable]
    public void HandleMouseMove(int x, int y)
    {
        message = $"Mouse move at {x}, {y}";
        StateHasChanged();
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            selfReference = DotNetObjectReference.Create(this);
            var minInterval = 500;

            await JS.InvokeVoidAsync("onThrottledMouseMove", 
                mouseMoveElement, selfReference, minInterval);
        }
    }

    public void Dispose() => selfReference?.Dispose();
}

Kode JavaScript yang sesuai mendaftarkan pendengar peristiwa DOM untuk gerakan mouse. Dalam contoh ini, pendengar peristiwa menggunakan throttle Lodash untuk membatasi tingkat pemanggilan:

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
<script>
  function onThrottledMouseMove(elem, component, interval) {
    elem.addEventListener('mousemove', _.throttle(e => {
      component.invokeMethodAsync('HandleMouseMove', e.offsetX, e.offsetY);
    }, interval));
  }
</script>

Hindari penyajian ulang setelah menangani peristiwa tanpa perubahan status

Komponen mewarisi dari ComponentBase, yang secara otomatis memanggil StateHasChanged setelah penanganan aktivitas komponen dipanggil. Dalam beberapa kasus, mungkin tidak perlu atau tidak diinginkan untuk memicu rerender setelah penanganan aktivitas dipanggil. Misalnya, penanganan aktivitas mungkin tidak mengubah status komponen. Dalam skenario ini, aplikasi dapat memanfaatkan IHandleEvent antarmuka untuk mengontrol perilaku Blazorpenanganan peristiwa.

Nota

Pendekatan di bagian ini tidak mengalirkan pengecualian ke batas kesalahan. Untuk informasi selengkapnya dan kode demonstrasi yang mendukung batas kesalahan dengan memanggil , lihat ComponentBase.DispatchExceptionAsync.dotnet/aspnetcore

Untuk mencegah rerender untuk semua penanganan aktivitas komponen, terapkan IHandleEvent dan berikan IHandleEvent.HandleEventAsync tugas yang memanggil penanganan aktivitas tanpa memanggil StateHasChanged.

Dalam contoh berikut, tidak ada penanganan aktivitas yang ditambahkan ke komponen yang memicu rerender, sehingga HandleSelect tidak menghasilkan render saat dipanggil.

HandleSelect1.razor:

@page "/handle-select-1"
@using Microsoft.Extensions.Logging
@implements IHandleEvent
@inject ILogger<HandleSelect1> Logger

<p>
    Last render DateTime: @dt
</p>

<button @onclick="HandleSelect">
    Select me (Avoids Rerender)
</button>

@code {
    private DateTime dt = DateTime.Now;

    private void HandleSelect()
    {
        dt = DateTime.Now;

        Logger.LogInformation("This event handler doesn't trigger a rerender.");
    }

    Task IHandleEvent.HandleEventAsync(
        EventCallbackWorkItem callback, object? arg) => callback.InvokeAsync(arg);
}

Selain mencegah rerender setelah penanganan aktivitas diaktifkan dalam komponen dengan cara global, dimungkinkan untuk mencegah rerender setelah satu penanganan aktivitas dengan menggunakan metode utilitas berikut.

Tambahkan kelas berikut EventUtil ke Blazor aplikasi. Tindakan statis dan fungsi di bagian EventUtil atas kelas menyediakan handler yang mencakup beberapa kombinasi argumen dan jenis pengembalian yang Blazor digunakan saat menangani peristiwa.

EventUtil.cs:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;

public static class EventUtil
{
    public static Action AsNonRenderingEventHandler(Action callback)
        => new SyncReceiver(callback).Invoke;
    public static Action<TValue> AsNonRenderingEventHandler<TValue>(
            Action<TValue> callback)
        => new SyncReceiver<TValue>(callback).Invoke;
    public static Func<Task> AsNonRenderingEventHandler(Func<Task> callback)
        => new AsyncReceiver(callback).Invoke;
    public static Func<TValue, Task> AsNonRenderingEventHandler<TValue>(
            Func<TValue, Task> callback)
        => new AsyncReceiver<TValue>(callback).Invoke;

    private record SyncReceiver(Action callback) 
        : ReceiverBase { public void Invoke() => callback(); }
    private record SyncReceiver<T>(Action<T> callback) 
        : ReceiverBase { public void Invoke(T arg) => callback(arg); }
    private record AsyncReceiver(Func<Task> callback) 
        : ReceiverBase { public Task Invoke() => callback(); }
    private record AsyncReceiver<T>(Func<T, Task> callback) 
        : ReceiverBase { public Task Invoke(T arg) => callback(arg); }

    private record ReceiverBase : IHandleEvent
    {
        public Task HandleEventAsync(EventCallbackWorkItem item, object arg) => 
            item.InvokeAsync(arg);
    }
}

Panggil EventUtil.AsNonRenderingEventHandler untuk memanggil penanganan aktivitas yang tidak memicu render saat dipanggil.

Pada contoh berikut:

  • Memilih tombol pertama, yang memanggil HandleClick1, memicu rerender.
  • Memilih tombol kedua, yang memanggil HandleClick2, tidak memicu rerender.
  • Memilih tombol ketiga, yang memanggil HandleClick3, tidak memicu rerender dan menggunakan argumen peristiwa (MouseEventArgs).

HandleSelect2.razor:

@page "/handle-select-2"
@using Microsoft.Extensions.Logging
@inject ILogger<HandleSelect2> Logger

<p>
    Last render DateTime: @dt
</p>

<button @onclick="HandleClick1">
    Select me (Rerenders)
</button>

<button @onclick="EventUtil.AsNonRenderingEventHandler(HandleClick2)">
    Select me (Avoids Rerender)
</button>

<button @onclick="EventUtil.AsNonRenderingEventHandler<MouseEventArgs>(HandleClick3)">
    Select me (Avoids Rerender and uses <code>MouseEventArgs</code>)
</button>

@code {
    private DateTime dt = DateTime.Now;

    private void HandleClick1()
    {
        dt = DateTime.Now;

        Logger.LogInformation("This event handler triggers a rerender.");
    }

    private void HandleClick2()
    {
        dt = DateTime.Now;

        Logger.LogInformation("This event handler doesn't trigger a rerender.");
    }
    
    private void HandleClick3(MouseEventArgs args)
    {
        dt = DateTime.Now;

        Logger.LogInformation(
            "This event handler doesn't trigger a rerender. " +
            "Mouse coordinates: {ScreenX}:{ScreenY}", 
            args.ScreenX, args.ScreenY);
    }
}

Selain menerapkan IHandleEvent antarmuka, memanfaatkan praktik terbaik lain yang dijelaskan dalam artikel ini juga dapat membantu mengurangi render yang tidak diinginkan setelah peristiwa ditangani. Misalnya, mengesampingkan ShouldRender komponen turunan dari komponen target dapat digunakan untuk mengontrol penyajian.

Hindari membuat ulang delegasi untuk banyak elemen atau komponen berulang

BlazorRekreasi ekspresi lambda delegasi untuk elemen atau komponen dalam perulangan dapat menyebabkan performa yang buruk.

Komponen berikut yang diperlihatkan dalam artikel penanganan peristiwa merender sekumpulan tombol. Setiap tombol menetapkan delegasi ke peristiwanya @onclick , yang tidak masalah jika tidak ada banyak tombol untuk dirender.

EventHandlerExample5.razor:

@page "/event-handler-example-5"

<h1>@heading</h1>

@for (var i = 1; i < 4; i++)
{
    var buttonNumber = i;

    <p>
        <button @onclick="@(e => UpdateHeading(e, buttonNumber))">
            Button #@i
        </button>
    </p>
}

@code {
    private string heading = "Select a button to learn its position";

    private void UpdateHeading(MouseEventArgs e, int buttonNumber)
    {
        heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
    }
}
@page "/event-handler-example-5"

<h1>@heading</h1>

@for (var i = 1; i < 4; i++)
{
    var buttonNumber = i;

    <p>
        <button @onclick="@(e => UpdateHeading(e, buttonNumber))">
            Button #@i
        </button>
    </p>
}

@code {
    private string heading = "Select a button to learn its position";

    private void UpdateHeading(MouseEventArgs e, int buttonNumber)
    {
        heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
    }
}

Jika sejumlah besar tombol dirender menggunakan pendekatan sebelumnya, kecepatan penyajian berdampak buruk yang menyebabkan pengalaman pengguna yang buruk. Untuk merender sejumlah besar tombol dengan panggilan balik untuk peristiwa klik, contoh berikut menggunakan kumpulan objek tombol yang menetapkan delegasi setiap tombol @onclick ke Action. Pendekatan berikut tidak perlu Blazor membangun kembali semua delegasi tombol setiap kali tombol dirender:

LambdaEventPerformance.razor:

@page "/lambda-event-performance"

<h1>@heading</h1>

@foreach (var button in Buttons)
{
    <p>
        <button @key="button.Id" @onclick="button.Action">
            Button #@button.Id
        </button>
    </p>
}

@code {
    private string heading = "Select a button to learn its position";

    private List<Button> Buttons { get; set; } = new();

    protected override void OnInitialized()
    {
        for (var i = 0; i < 100; i++)
        {
            var button = new Button();

            button.Id = Guid.NewGuid().ToString();

            button.Action = (e) =>
            {
                UpdateHeading(button, e);
            };

            Buttons.Add(button);
        }
    }

    private void UpdateHeading(Button button, MouseEventArgs e)
    {
        heading = $"Selected #{button.Id} at {e.ClientX}:{e.ClientY}";
    }

    private class Button
    {
        public string? Id { get; set; }
        public Action<MouseEventArgs> Action { get; set; } = e => { };
    }
}