ASP.NET Temel Blazor performans en iyi yöntemleri

Not

Bu, bu makalenin en son sürümü değildir. Geçerli sürüm için bu makalenin .NET 8 sürümüne bakın.

Önemli

Bu bilgiler, ticari olarak piyasaya sürülmeden önce önemli ölçüde değiştirilebilen bir yayın öncesi ürünle ilgilidir. Burada verilen bilgilerle ilgili olarak Microsoft açık veya zımni hiçbir garanti vermez.

Geçerli sürüm için bu makalenin .NET 8 sürümüne bakın.

Blazor en gerçekçi uygulama kullanıcı arabirimi senaryolarında yüksek performans için iyileştirilmiştir. Ancak en iyi performans, geliştiricilerin doğru desenleri ve özellikleri benimsemesine bağlıdır.

Not

Bu makaledeki kod örnekleri, .NET 6 veya sonraki sürümlerindeki ASP.NET Core'da desteklenen null atanabilir başvuru türlerini (NTS) ve .NET derleyici null durum statik analizini benimser.

İşleme hızını iyileştirme

İşleme iş yükünü en aza indirmek ve kullanıcı arabirimi yanıt hızını iyileştirmek için işleme hızını iyileştirin; bu da ui işleme hızında on kat veya daha yüksek bir iyileştirme sağlayabilir.

Bileşen alt ağaçlarının gereksiz şekilde işlenmesini önleyin

Bir olay oluştuğunda alt bileşen alt ağaçlarının yeniden bakımını atlayarak bir üst bileşenin işleme maliyetinin çoğunu kaldırabilirsiniz. Yalnızca işlenmesi özellikle pahalı olan ve kullanıcı arabirimi gecikmesine neden olan yeniden oluşturma alt ağaçlarını atlama konusunda kaygılanmanız gerekir.

Çalışma zamanında bileşenler hiyerarşide bulunur. Kök bileşenin (yüklenen ilk bileşen) alt bileşenleri vardır. Buna karşılık, kökün alt öğelerinin kendi alt bileşenleri vardır ve bu şekilde devam eder. Kullanıcının düğme seçmesi gibi bir olay oluştuğunda, aşağıdaki işlem hangi bileşenlerin yeniden ayarleneceğini belirler:

  1. Olay, olayın işleyicisini işleyen bileşene gönderilir. Olay işleyicisi yürütüldikten sonra bileşen yeniden oluşturulur.
  2. Bir bileşen yeniden başlatıldığında, her bir alt bileşenine parametre değerlerinin yeni bir kopyasını sağlar.
  3. Yeni parametre değerleri kümesi alındıktan sonra, her bileşen yeniden oluşturup oluşturmamaya karar verir. Varsayılan olarak, parametre değerleri değişmiş olabilirse ( örneğin, değiştirilebilir nesnelerse) bileşenler yeniden değiştirilebilir.

Önceki sıranın son iki adımı, bileşen hiyerarşisinde özyinelemeli olarak devam eder. Çoğu durumda alt ağacın tamamı yeniden oluşturulur. Üst düzey bileşenleri hedefleyen olaylar, üst düzey bileşenin altındaki her bileşenin yeniden rerender yapması gerektiğinden pahalı bir şekilde yeniden anahtarlamalara neden olabilir.

Belirli bir alt ağaçta işleme özyinelemesini önlemek için aşağıdaki yaklaşımlardan birini kullanın:

  • Alt bileşen parametrelerinin , int, bool, DateTimeve gibi stringtemel sabit türlerden ve diğer benzer türlerden olduğundan emin olun. Değişiklikleri algılamaya yönelik yerleşik mantık, ilkel sabit parametre değerleri değişmediyse yeniden boyutlandırmayı otomatik olarak atlar. Bir alt bileşeni ile <Customer CustomerId="@item.CustomerId" />işlerseniz, burada CustomerId bir int türdür, bileşen Customer değişmediği sürece item.CustomerId yeniden oluşturulmaz.
  • Geçersiz kıl ShouldRender:
    • Karmaşık özel model türleri, olay geri çağırmaları veya RenderFragment değerler gibi ayrıcalıksız parametre değerlerini kabul etmek için.
    • İlk işlemeden sonra değişmeyen bir yalnızca kullanıcı arabirimi bileşeni yazarsanız, parametre değeri değişirse değişsin.

Aşağıdaki havayolu uçuş arama aracı örneği, değişiklikleri algılamak için gerekli bilgileri izlemek için özel alanları kullanır. Önceki gelen uçuş tanımlayıcısı (prevInboundFlightId) ve önceki giden uçuş tanımlayıcısı (prevOutboundFlightId) bir sonraki olası bileşen güncelleştirmesi için bilgileri izler. Bileşenin parametreleri içinde OnParametersSetayarlandığında uçuş tanımlayıcılarından biri değişirse, bileşen olarak ayarlandığından trueyeniden shouldRender oluşturulur. Uçuş tanımlayıcıları false denetlendikten sonra değerlendirilirse shouldRender pahalı bir rerender kullanmaktan kaçınılır:

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

Olay işleyicisi olarak da ayarlanabilir shouldRendertrue. Çoğu bileşen için, tek tek olay işleyicileri düzeyinde rerendering belirlemek genellikle gerekli değildir.

Daha fazla bilgi edinmek için aşağıdaki kaynaklara bakın:

Sanallaştırma

Binlerce girdi içeren bir liste veya kılavuz gibi bir döngü içinde büyük miktarda kullanıcı arabirimi işlenirken, işleme işlemlerinin büyük miktarı ui işlemede gecikmeye neden olabilir. Kullanıcının kaydırmadan aynı anda yalnızca az sayıda öğe görebildiği göz önüne alındığında, şu anda görünür olmayan öğeleri işlemek için zaman harcamak genellikle boşa harcanabilir.

BlazorVirtualize<TItem> yalnızca geçerli kaydırma görünüm penceresi içindeki liste öğelerini işlerken rastgele büyük bir listenin görünümünü ve kaydırma davranışlarını oluşturmak için bileşeni sağlar. Örneğin, bir bileşen 100.000 giriş içeren bir liste işleyebilir, ancak yalnızca görünür olan 20 öğenin işleme maliyetini ödeyebilir.

Daha fazla bilgi için bkz . ASP.NET Temel Razor bileşen sanallaştırma.

Basit, iyileştirilmiş bileşenler oluşturma

Çoğu Razor bileşen kullanıcı arabiriminde tekrarlamadığından ve yüksek sıklıkta yeniden çalışmadığından çoğu bileşen için agresif iyileştirme çalışmaları gerekmez. Örneğin, yönergesi @page olan yönlendirilebilir bileşenler ve iletişim kutuları veya formlar gibi kullanıcı arabiriminin üst düzey parçalarını işlemek için kullanılan bileşenler büyük olasılıkla tek seferde yalnızca bir tane görünür ve kullanıcı hareketine yanıt olarak yalnızca rerender görünür. Bu bileşenler genellikle yüksek işleme iş yükü oluşturmaz, bu nedenle işleme performansı konusunda fazla endişe duymadan çerçeve özelliklerinin herhangi bir bileşimini serbestçe kullanabilirsiniz.

Ancak, bileşenlerin büyük ölçekte tekrarlandığı ve genellikle düşük kullanıcı arabirimi performansına neden olduğu yaygın senaryolar vardır:

  • Girişler veya etiketler gibi yüzlerce ayrı öğe içeren büyük iç içe yerleştirilmiş formlar.
  • Yüzlerce satır veya binlerce hücre içeren kılavuzlar.
  • Milyonlarca veri noktası içeren dağılımlar.

Her öğeyi, hücreyi veya veri noktasını ayrı bir bileşen örneği olarak modellediyseniz, bunların çoğu genellikle işleme performansı kritik hale gelir. Bu bölüm, kullanıcı arabiriminin hızlı ve hızlı yanıt vermesi için bu tür bileşenleri basit hale getirme konusunda öneriler sağlar.

Binlerce bileşen örneğinden kaçının

Her bileşen, ebeveynlerinden ve çocuklarından bağımsız olarak işlenebilen ayrı bir adadır. Kullanıcı arabirimini bir bileşen hiyerarşisine bölmeyi seçerek, kullanıcı arabirimi işlemenin ayrıntı düzeyi üzerinde denetim sahibi olacaksınız. Bu, iyi veya düşük performansa neden olabilir.

Kullanıcı arabirimini ayrı bileşenlere bölerek, olaylar oluştuğunda kullanıcı arabiriminin daha küçük bölümlerine sahip olabilirsiniz. Her satırda düğmesi olan birçok satırın yer aldığı bir tabloda, sayfanın veya tablonun tamamı yerine bir alt bileşen kullanarak yalnızca bu tek satırlı rerender'e sahip olabilirsiniz. Ancak her bileşenin bağımsız durumu ve işleme yaşam döngüsüyle başa çıkabilmek için ek bellek ve CPU ek yükü gerekir.

ASP.NET Core ürün birimi mühendisleri tarafından gerçekleştirilen bir testte, bir uygulamada bileşen örneği başına yaklaşık 0,06 ms işleme yükü görüldü Blazor WebAssembly . Test uygulaması üç parametre kabul eden basit bir bileşen işledi. Dahili olarak, ek yük büyük ölçüde sözlüklerden bileşen başına durumun alınmasından ve parametrelerin geçirilmesinden ve alınmasından kaynaklanır. Çarpma işlemiyle, 2.000 ek bileşen örneği eklenmesinin işleme süresine 0,12 saniye ekleyeceğini ve kullanıcı arabiriminin kullanıcılara yavaş hissetmeye başlayacağını görebilirsiniz.

Daha fazla bileşene sahip olabilmeniz için bileşenleri daha hafif hale getirmek mümkündür. Ancak, daha güçlü bir teknik genellikle işlenmek üzere bu kadar çok bileşenin olmasını önlemektir. Aşağıdaki bölümlerde, gerçekleştirebileceğiniz iki yaklaşım açıklanmaktadır.

Bellek yönetimi hakkında daha fazla bilgi için bkz . ASP.NET Core sunucu tarafı Blazor uygulamaları barındırma ve dağıtma.

Alt bileşenleri üst öğelerine satır içi olarak ekleme

Bir üst bileşenin alt bileşenleri döngüde işleyen aşağıdaki bölümünü göz önünde bulundurun:

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

Yukarıdaki örnek, aynı anda binlerce ileti gösterilmiyorsa iyi performans gösterir. Aynı anda binlerce ileti göstermek için ayrı ChatMessageDisplay bileşeni hesaba katmamayı göz önünde bulundurun. Bunun yerine, alt bileşeni üst bileşene satır içi olarak ekleyin. Aşağıdaki yaklaşım, her alt bileşenin işaretlemesini bağımsız olarak yeniden oluşturma yeteneğini kaybetme pahasına bu kadar çok alt bileşeni işlemenin bileşen başına ek yükünü önler:

<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>
Kodda yeniden kullanılabilir RenderFragments tanımlama

Alt bileşenleri yalnızca işleme mantığını yeniden kullanmaya yönelik bir yöntem olarak factoriz olabilir. Bu durumda, ek bileşenler uygulamadan yeniden kullanılabilir işleme mantığı oluşturabilirsiniz. Herhangi bir bileşenin @code bloğunda bir RenderFragmenttanımlayın. Parçanın herhangi bir konumdan gerektiği kadar işlenmesi:

@RenderWelcomeInfo

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

@RenderWelcomeInfo

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

Kodu birden çok bileşen arasında yeniden kullanılabilir hale getirmek RenderTreeBuilder için ve staticbildirinRenderFragmentpublic:

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

SayHello yukarıdaki örnekte ilişkisiz bir bileşenden çağrılabilir. Bu teknik, bileşen başına ek yük olmadan işlenen yeniden kullanılabilir işaretleme parçacıklarından oluşan kitaplıklar oluşturmak için kullanışlıdır.

RenderFragment temsilciler parametreleri kabul edebilir. Aşağıdaki bileşen, iletiyi (message) temsilciye RenderFragment geçirir:

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

Yukarıdaki yaklaşım, bileşen başına ek yük olmadan işleme mantığını yeniden kullanır. Ancak yaklaşım, kullanıcı arabiriminin alt ağacının bağımsız olarak yenilenmesine izin vermez ve bileşen sınırı olmadığından üst öğesi işlenirken kullanıcı arabiriminin alt ağacını işlemeyi atlayabilir. Temsilciye RenderFragment atama yalnızca bileşen dosyalarında Razor (.razor ) desteklenir ve olay geri çağırmaları desteklenmez.

Aşağıdaki örnekte olduğu gibi TitleTemplate bir alan başlatıcısı tarafından başvurulaamayan statik olmayan bir alan, yöntem veya özellik için RenderFragmentiçin bir alan yerine bir özellik kullanın:

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

Çok fazla parametre alma

Bir bileşen, örneğin yüzlerce veya binlerce kez son derece sık yineleniyorsa, her parametreyi geçirme ve alma yükü oluşur.

Çok fazla parametrenin performansı ciddi şekilde kısıtlaması nadirdir, ancak bir faktör olabilir. Bir TableCell kılavuz içinde 4.000 kez işleyen bir bileşen için, bileşene geçirilen her parametre toplam işleme maliyetine yaklaşık 15 ms ekler. On parametrenin geçirilmesi yaklaşık 150 ms gerektirir ve kullanıcı arabirimi işleme gecikmesine neden olur.

Parametre yükünü azaltmak için özel bir sınıfta birden çok parametreyi paketleyin. Örneğin, tablo hücresi bileşeni ortak bir nesneyi kabul edebilir. Aşağıdaki örnekte, Data her hücre için farklıdır, ancak Options tüm hücre örneklerinde ortaktır:

@typeparam TItem

...

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

Ancak, yukarıdaki örnekte gösterildiği gibi tablo hücresi bileşeni olmamasının bir geliştirme olabileceğini ve bunun yerine mantığının üst bileşene satır içi olarak girilebileceğini göz önünde bulundurun.

Not

Performansı geliştirmek için birden çok yaklaşım mevcut olduğunda, hangi yaklaşımın en iyi sonuçları verdiğini belirlemek için yaklaşımları karşılaştırmak genellikle gereklidir.

Genel tür parametreleri ()@typeparam hakkında daha fazla bilgi için aşağıdaki kaynaklara bakın:

Basamaklı parametrelerin sabit olduğundan emin olun

Bileşenin CascadingValue isteğe bağlı IsFixed bir parametresi vardır:

  • (varsayılan) ise IsFixedfalse , basamaklı değerin her alıcısı değişiklik bildirimlerini almak için bir abonelik ayarlar. Abonelik izleme nedeniyle her [CascadingParameter] biri normalden [Parameter] önemli ölçüde daha pahalıdır.
  • Ise IsFixedtrue (örneğin, <CascadingValue Value="someValue" IsFixed="true">), alıcılar ilk değeri alır ancak güncelleştirmeleri almak için bir abonelik ayarlamaz. Her [CascadingParameter] biri hafiftir ve normal [Parameter]bir değerinden daha pahalı değildir.

true Basamaklı değeri alan çok sayıda başka bileşen varsa, bu ayar IsFixed performansı artırır. Mümkün olduğunca basamaklı değerlerde olarak ayarlayın IsFixedtrue . Sağlanan değerin zaman içinde değişmediği zamanı olarak ayarlayabilirsiniz IsFixedtrue .

Bir bileşenin this basamaklı değer olarak geçtiği durumlarda olarak IsFixed da ayarlanabilir true:

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

Daha fazla bilgi için bkz . ASP.NET Core Blazor basamaklı değerleri ve parametreleri.

ile öznitelik sıçramasından kaçının CaptureUnmatchedValues

Bileşenler bayrağını kullanarak CaptureUnmatchedValues "eşleşmeyen" parametre değerlerini almayı seçebilir:

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

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

Bu yaklaşım, öğesine rastgele ek öznitelikler geçirilmesine olanak tanır. Ancak işleyicinin şunları yapması gerektiğinden bu yaklaşım pahalıdır:

  • Sözlük oluşturmak için sağlanan tüm parametreleri bilinen parametreler kümesiyle eşleştirin.
  • Aynı özniteliğin birden çok kopyasının birbirinin üzerine nasıl yazılmasını izleyin.

Sık tekrarlanmayan bileşenler gibi bileşen işleme performansının kritik olmadığı yerlerde kullanın CaptureUnmatchedValues . Büyük bir listedeki veya kılavuzun hücrelerindeki her öğe gibi büyük ölçekte işlenen bileşenler için öznitelik sıçramasını önlemeyi deneyin.

Daha fazla bilgi için bkz . ASP.NET Core Blazor özniteliğinin sıçraması ve rastgele parametreler.

El ile uygulama SetParametersAsync

Bileşen başına işleme ek yükünün önemli bir kaynağı, özelliklere [Parameter] gelen parametre değerleri yazmaktır. oluşturucu, parametre değerlerini yazmak için yansıma kullanır ve bu da büyük ölçekte düşük performansa neden olabilir.

Bazı aşırı durumlarda yansımayı önlemek ve kendi parametre ayarı mantığınızı el ile uygulamak isteyebilirsiniz. Bu, aşağıdaki durumlarda geçerli olabilir:

  • Bir bileşen, örneğin kullanıcı arabiriminde bileşenin yüzlerce veya binlerce kopyası olduğunda son derece sık işlenir.
  • Bir bileşen birçok parametre kabul eder.
  • Alıcı parametrelerin ek yükünün kullanıcı arabirimi yanıt hızı üzerinde gözlemlenebilir bir etkisi olduğunu fark edebilirsiniz.

Aşırı durumlarda, bileşenin sanal SetParametersAsync yöntemini geçersiz kılabilir ve kendi bileşene özgü mantığınızı uygulayabilirsiniz. Aşağıdaki örnek, sözlük aramalarını kasıtlı olarak önler:

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

Önceki kodda temel sınıfı SetParametersAsync döndürmek, parametreleri yeniden atamadan normal yaşam döngüsü yöntemini çalıştırır.

Yukarıdaki kodda görebileceğiniz gibi özel mantığı geçersiz kılma SetParametersAsync ve sağlama karmaşık ve zahmetli olduğundan bu yaklaşımın benimsenmesi genel olarak önerilmez. Aşırı durumlarda işleme performansını %20-25 oranında artırabilir, ancak bu yaklaşımı yalnızca bu bölümün önceki bölümlerinde listelenen aşırı senaryolarda dikkate almanız gerekir.

Olayları çok hızlı tetikleme

Bazı tarayıcı olayları son derece sık tetikler. Örneğin ve onmousemoveonscroll saniyede on veya yüzlerce kez ateşleyebilir. Çoğu durumda, kullanıcı arabirimi güncelleştirmelerini bu sıklıkta gerçekleştirmeniz gerekmez. Olaylar çok hızlı tetiklenirse, kullanıcı arabirimi yanıt verme hızına zarar verebilir veya aşırı CPU süresi tüketebilirsiniz.

Hızla tetikleyen yerel olayları kullanmak yerine, daha az sıklıkta tetikleyen bir geri çağırma kaydetmek için birlikte çalışma kullanımını JS göz önünde bulundurun. Örneğin, aşağıdaki bileşen farenin konumunu görüntüler ancak her 500 ms'de en fazla bir kez güncelleştirilir:

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

İlgili JavaScript kodu DOM olay dinleyicisini fare hareketi için kaydeder. Bu örnekte, olay dinleyicisi çağrıların oranını sınırlamak için Lodash throttle işlevini kullanır:

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

Durum değişiklikleri olmadan olayları işledikten sonra yeniden oturum açmaktan kaçının

Varsayılan olarak, bileşenler bileşenin olay işleyicileri StateHasChanged çağrıldıktan sonra otomatik olarak çağrılan öğesinden ComponentBasedevralır. Bazı durumlarda, bir olay işleyicisi çağrıldıktan sonra bir rerender tetikleme gereksiz veya istenmeyen olabilir. Örneğin, bir olay işleyicisi bileşen durumunu değiştiremeyebilir. Bu senaryolarda uygulama, 'nin olay işleme davranışını Blazordenetlemek için arabiriminden yararlanabilirIHandleEvent.

Bir bileşenin olay işleyicilerinin tümü için rerender'ları önlemek için, öğesini çağırmadan StateHasChangedolay işleyicisini çağıran bir IHandleEvent.HandleEventAsync görev uygulayın IHandleEvent ve sağlayın.

Aşağıdaki örnekte, bileşene eklenen hiçbir olay işleyicisi bir rerender tetiklemez, bu nedenle HandleSelect çağrıldığında rerender ile sonuçlanmıyor.

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

Olay işleyicileri bir bileşende genel bir şekilde tetiklendikten sonra rerender'ları önlemeye ek olarak, aşağıdaki yardımcı program yöntemini kullanarak tek bir olay işleyicisi sonrasında rerender'ları önlemek de mümkündür.

Aşağıdaki EventUtil sınıfı bir Blazor uygulamaya ekleyin. Sınıfın EventUtil en üstündeki statik eylemler ve işlevler, olayları işlerken kullanılan çeşitli bağımsız değişkenleri ve dönüş türlerini kapsayan Blazor işleyiciler sağlar.

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

Çağrıldığında bir işleme tetikleme tetiklemeyen bir olay işleyici çağırmak için çağrısı EventUtil.AsNonRenderingEventHandler .

Aşağıdaki örnekte:

  • öğesini çağıran HandleClick1ilk düğmenin seçilmesi bir rerender tetikler.
  • öğesini çağıran HandleClick2ikinci düğme seçildiğinde bir rerender tetiklemez.
  • öğesini çağıran HandleClick3üçüncü düğmenin seçilmesi, bir rerender tetiklemez ve olay bağımsız değişkenlerini (MouseEventArgs kullanır).

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

Arabirimi uygulamaya IHandleEvent ek olarak, bu makalede açıklanan diğer en iyi uygulamalardan yararlanılması, olaylar işlendikten sonra istenmeyen işlemelerin azaltılmasına da yardımcı olabilir. Örneğin, yeniden boyutlandırmayı denetlemek için hedef bileşenin alt bileşenlerinde geçersiz ShouldRender kılma kullanılabilir.

Yinelenen birçok öğe veya bileşen için temsilcileri yeniden oluşturmaktan kaçının

Blazor'nin bir döngüdeki öğeler veya bileşenler için lambda ifadesi temsilcilerinin yeniden üretileri düşük performansa yol açabilir.

Olay işleme makalesinde gösterilen aşağıdaki bileşen bir dizi düğmeyi işler. Her düğme olayına @onclick bir temsilci atar; işlenmek için çok fazla düğme yoksa bu sorun olmaz.

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

Yukarıdaki yaklaşım kullanılarak çok sayıda düğme işlenirse, işleme hızı olumsuz etkilenir ve bu da kötü bir kullanıcı deneyimine neden olur. Tıklama olayları için geri çağırma ile çok sayıda düğmeyi işlemek için, aşağıdaki örnekte her düğmenin temsilcisini bir Actionöğesine atayan bir düğme @onclick nesneleri koleksiyonu kullanılır. Aşağıdaki yaklaşım, düğmeler her işlendiği zaman tüm düğme temsilcilerinin yeniden oluşturulmasını gerektirmez Blazor :

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 => { };
    }
}

JavaScript birlikte çalışma hızını iyileştirme

.NET ve JavaScript arasındaki çağrılar ek yük gerektirir çünkü:

  • Varsayılan olarak, çağrılar zaman uyumsuz durumdadır.
  • Varsayılan olarak, .NET ve JavaScript türleri arasında anlaşılması kolay bir dönüştürme mekanizması sağlamak için parametreler ve dönüş değerleri JSON serileştirilmiştir.

Ayrıca, sunucu tarafı Blazor uygulamalar için bu çağrılar ağ üzerinden geçirilir.

Aşırı ayrıntılı aramalardan kaçının

Her çağrı biraz ek yük içerdiğinden, çağrı sayısını azaltmak değerli olabilir. Bir öğe koleksiyonunu tarayıcının localStorageiçinde depolayan aşağıdaki kodu göz önünde bulundurun:

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
    foreach (var item in items)
    {
        await JS.InvokeVoidAsync("localStorage.setItem", item.Id, 
            JsonSerializer.Serialize(item));
    }
}

Yukarıdaki örnek, her öğe için ayrı JS bir birlikte çalışma çağrısı yapar. Bunun yerine, aşağıdaki yaklaşım birlikte çalışma işlemini JS tek bir çağrıya düşürür:

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
    await JS.InvokeVoidAsync("storeAllInLocalStorage", items);
}

Karşılık gelen JavaScript işlevi, tüm öğe koleksiyonunu istemcide depolar:

function storeAllInLocalStorage(items) {
  items.forEach(item => {
    localStorage.setItem(item.id, JSON.stringify(item));
  });
}

Uygulamalar için Blazor WebAssembly tek tek JS birlikte çalışma çağrılarının tek bir çağrıya aktarılması genellikle yalnızca bileşen çok sayıda JS birlikte çalışma çağrısı yaptığında performansı önemli ölçüde artırır.

Zaman uyumlu çağrıların kullanımını göz önünde bulundurun

.NET’ten JavaScript’i çağırma

Bu bölüm yalnızca istemci tarafı bileşenleri için geçerlidir.

JS interop çağrıları, çağrılan kodun zaman uyumlu veya zaman uyumsuz olmasına bakılmaksızın varsayılan olarak zaman uyumsuzdur. Bileşenlerin sunucu tarafı ve istemci tarafı işleme modları arasında uyumlu olduğundan emin olmak için çağrılar varsayılan olarak zaman uyumsuz olarak yapılır. Sunucuda, tüm JS birlikte çalışma çağrıları bir ağ bağlantısı üzerinden gönderildiğinden zaman uyumsuz olmalıdır.

Bileşeninizin yalnızca WebAssembly üzerinde çalıştığından JS eminseniz zaman uyumlu birlikte çalışma çağrıları yapmayı seçebilirsiniz. Bu, zaman uyumsuz çağrılar yapmaktan biraz daha az ek yüke sahiptir ve sonuçları beklerken ara durum olmadığından daha az işleme döngüsüne neden olabilir.

İstemci tarafı bileşeninde .NET'ten JavaScript'e zaman uyumlu bir çağrı yapmak içinIJSInProcessRuntime, birlikte çalışma çağrısı yapmak için öğesine yayın IJSRuntime yapınJS:

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

ASP.NET Core 5.0 veya üzeri istemci tarafı bileşenleriyle IJSObjectReference çalışırken, bunun yerine zaman uyumlu olarak kullanabilirsiniz IJSInProcessObjectReference . IJSInProcessObjectReferenceIAsyncDisposable/IDisposable uygular ve aşağıdaki örnekte gösterildiği gibi bellek sızıntısını önlemek için çöp toplama için atılmalıdır:

@inject IJSRuntime JS
@implements IAsyncDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", 
            "./scripts.js");
        }
    }

    ...

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
}

JavaScript’ten .NET’i çağırma

Bu bölüm yalnızca istemci tarafı bileşenleri için geçerlidir.

JS interop çağrıları, çağrılan kodun zaman uyumlu veya zaman uyumsuz olmasına bakılmaksızın varsayılan olarak zaman uyumsuzdur. Bileşenlerin sunucu tarafı ve istemci tarafı işleme modları arasında uyumlu olduğundan emin olmak için çağrılar varsayılan olarak zaman uyumsuz olarak yapılır. Sunucuda, tüm JS birlikte çalışma çağrıları bir ağ bağlantısı üzerinden gönderildiğinden zaman uyumsuz olmalıdır.

Bileşeninizin yalnızca WebAssembly üzerinde çalıştığından JS eminseniz zaman uyumlu birlikte çalışma çağrıları yapmayı seçebilirsiniz. Bu, zaman uyumsuz çağrılar yapmaktan biraz daha az ek yüke sahiptir ve sonuçları beklerken ara durum olmadığından daha az işleme döngüsüne neden olabilir.

İstemci tarafı bileşeninde JavaScript'ten .NET'e zaman uyumlu bir çağrı yapmak için yerine DotNet.invokeMethodAsynckullanınDotNet.invokeMethod.

Zaman uyumlu çağrılar şu durumlarda çalışır:

  • Bileşen yalnızca WebAssembly'de yürütülmeye yönelik olarak işlenir.
  • Çağrılan işlev zaman uyumlu olarak bir değer döndürür. İşlev bir async yöntem değildir ve .NET Task veya JavaScript Promisedöndürmez.

Bu bölüm yalnızca istemci tarafı bileşenleri için geçerlidir.

JS interop çağrıları, çağrılan kodun zaman uyumlu veya zaman uyumsuz olmasına bakılmaksızın varsayılan olarak zaman uyumsuzdur. Bileşenlerin sunucu tarafı ve istemci tarafı işleme modları arasında uyumlu olduğundan emin olmak için çağrılar varsayılan olarak zaman uyumsuz olarak yapılır. Sunucuda, tüm JS birlikte çalışma çağrıları bir ağ bağlantısı üzerinden gönderildiğinden zaman uyumsuz olmalıdır.

Bileşeninizin yalnızca WebAssembly üzerinde çalıştığından JS eminseniz zaman uyumlu birlikte çalışma çağrıları yapmayı seçebilirsiniz. Bu, zaman uyumsuz çağrılar yapmaktan biraz daha az ek yüke sahiptir ve sonuçları beklerken ara durum olmadığından daha az işleme döngüsüne neden olabilir.

İstemci tarafı bileşeninde .NET'ten JavaScript'e zaman uyumlu bir çağrı yapmak içinIJSInProcessRuntime, birlikte çalışma çağrısı yapmak için öğesine yayın IJSRuntime yapınJS:

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

ASP.NET Core 5.0 veya üzeri istemci tarafı bileşenleriyle IJSObjectReference çalışırken, bunun yerine zaman uyumlu olarak kullanabilirsiniz IJSInProcessObjectReference . IJSInProcessObjectReferenceIAsyncDisposable/IDisposable uygular ve aşağıdaki örnekte gösterildiği gibi bellek sızıntısını önlemek için çöp toplama için atılmalıdır:

@inject IJSRuntime JS
@implements IAsyncDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", 
            "./scripts.js");
        }
    }

    ...

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
}

Özetlenmemiş çağrıların kullanımını göz önünde bulundurun

Bu bölüm yalnızca uygulamalar için Blazor WebAssembly geçerlidir.

üzerinde Blazor WebAssemblyçalışırken .NET'ten JavaScript'e özetlenmemiş çağrılar yapmak mümkündür. Bunlar, bağımsız değişkenlerin veya dönüş değerlerinin ON serileştirmesini gerçekleştirmeyen JSzaman uyumlu çağrılardır. .NET ve JavaScript gösterimleri arasındaki bellek yönetimi ve çevirilerin tüm yönleri geliştiriciye bırakılmıştır.

Uyarı

birlikte çalışma yaklaşımlarında kullanımın IJSUnmarshalledRuntime en az yükü JS olsa da, bu API'lerle etkileşim kurmak için gereken JavaScript API'leri şu anda belgelenmemiştir ve gelecek sürümlerde hataya neden olan değişikliklere tabidir.

function jsInteropCall() {
  return BINDING.js_to_mono_obj("Hello world");
}
@inject IJSRuntime JS

@code {
    protected override void OnInitialized()
    {
        var unmarshalledJs = (IJSUnmarshalledRuntime)JS;
        var value = unmarshalledJs.InvokeUnmarshalled<string>("jsInteropCall");
    }
}

JavaScript [JSImport]/[JSExport] birlikte çalışma kullanma

Uygulamalar için Blazor WebAssembly JavaScript[JSExport]/[JSImport] birlikte çalışma, .NET 7'de ASP.NET Core'dan önceki çerçeve sürümlerinde birlikte çalışma API'sine göre JS gelişmiş performans ve kararlılık sunar.

Daha fazla bilgi için bkz. ASP.NET Core Blazorile JavaScript JSİçeri/JSDışarı Aktarma birlikte çalışma .

Önceden (AOT) derleme

Önceden (AOT) derlemesi, tarayıcı tarafından doğrudan yürütülmesi için uygulamanın Blazor .NET kodunu doğrudan yerel WebAssembly'de derler. AOT ile derlenmiş uygulamalar, indirilmesi daha uzun süren daha büyük uygulamalarla sonuçlanır, ancak AOT ile derlenen uygulamalar, özellikle yoğun CPU kullanan görevler yürüten uygulamalar için genellikle daha iyi çalışma zamanı performansı sağlar. Daha fazla bilgi için bkz . ASP.NET Core Blazor WebAssembly derleme araçları ve önceden (AOT) derleme.

Uygulama indirme boyutunu simge durumuna küçültme

Çalışma zamanı yeniden bağlama

Çalışma zamanı yeniden bağlantısının bir uygulamanın indirme boyutunu nasıl en aza indirdiğini öğrenmek için bkz . ASP.NET Core Blazor WebAssembly derleme araçları ve önceden (AOT) derleme.

System.Text.Json komutunu kullanma

Blazor'nin JS birlikte çalışma uygulaması, düşük bellek ayırmaya sahip yüksek performanslı JSon serileştirme kitaplığı olan öğesine dayanırSystem.Text.Json. kullanımı System.Text.Json , bir veya daha fazla alternatif JSON kitaplığı eklemeye ek uygulama yükü boyutuna neden olmamalıdır.

Geçiş kılavuzu için bkz . 'den Newtonsoft.Json 'a System.Text.Jsongeçiş.

Ara Dil (IL) kırpma

Bu bölüm yalnızca uygulamalar için Blazor WebAssembly geçerlidir.

Bir uygulamadan Blazor WebAssembly kullanılmayan derlemelerin kırpılması, uygulamanın ikili dosyalarındaki kullanılmayan kodu kaldırarak uygulamanın boyutunu küçültür. Daha fazla bilgi için bkz . ASP.NET Core Blazoriçin Düzeltici'yi yapılandırma.

Bir Blazor WebAssembly uygulamanın bağlanması, uygulamanın ikili dosyalarındaki kullanılmayan kodu kırparak uygulamanın boyutunu küçültür. Varsayılan olarak, Ara Dil (IL) Bağlayıcısı yalnızca yapılandırmada Release oluşturulurken etkinleştirilir. Bundan yararlanmak için -c|--yapılandırma seçeneği olarak ayarlanmış Releasekomutunu kullanarak dotnet publish uygulamayı dağıtım için yayımlayın:

dotnet publish -c Release

Gecikmeli yük derlemeleri

Bu bölüm yalnızca uygulamalar için Blazor WebAssembly geçerlidir.

Derlemeler bir yol için gerekli olduğunda derlemeleri çalışma zamanında yükleyin. Daha fazla bilgi için bkz . ASP.NET Core'da Blazor WebAssemblygecikmeli yükleme derlemeleri.

Sıkıştırma

Bu bölüm yalnızca uygulamalar için Blazor WebAssembly geçerlidir.

Bir Blazor WebAssembly uygulama yayımlandığında, uygulamanın boyutunu küçültmek ve çalışma zamanı sıkıştırma ek yükünü kaldırmak için çıkış yayımlama sırasında statik olarak sıkıştırılır. Blazor , içerik anlaşması gerçekleştirmek ve statik olarak sıkıştırılmış dosyalar sunmak için sunucuya dayanır.

Bir uygulama dağıtıldıktan sonra, uygulamanın sıkıştırılmış dosyalara hizmet ettiğini doğrulayın. Tarayıcının geliştirici araçlarında sekmesini inceleyin ve dosyaların (Brotli sıkıştırması) veya Content-Encoding: gz (Gzip sıkıştırması) sunulduğundan Content-Encoding: br emin olun. Konak sıkıştırılmış dosyalar sunmuyorsa, Konak ve ASP.NET Core'u Blazor WebAssemblydağıtma başlığındaki yönergeleri izleyin.

Kullanılmayan özellikleri devre dışı bırakma

Bu bölüm yalnızca uygulamalar için Blazor WebAssembly geçerlidir.

Blazor WebAssembly'nin çalışma zamanı, daha küçük bir yük boyutu için devre dışı bırakılabilir aşağıdaki .NET özelliklerini içerir:

  • Saat dilimi bilgilerini doğru yapmak için bir veri dosyası eklenir. Uygulama bu özelliği gerektirmiyorsa, uygulamanın proje dosyasındaki BlazorEnableTimeZoneSupportfalseMSBuild özelliğini olarak ayarlayarak devre dışı bırakmayı göz önünde bulundurun:

    <PropertyGroup>
      <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
    </PropertyGroup>
    
  • Harmanlama bilgileri, gibi StringComparison.InvariantCultureIgnoreCase API'lerin düzgün çalışmasını sağlamak için eklenir. Uygulamanın harmanlama verilerini gerektirmediğinden eminseniz, uygulamanın proje dosyasındaki falseMSBuild özelliğini olarak ayarlayarak BlazorWebAssemblyPreserveCollationData bunu devre dışı bırakmayı göz önünde bulundurun:

    <PropertyGroup>
      <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
    </PropertyGroup>
    
  • Varsayılan olarak, Blazor WebAssembly tarihler ve para birimi gibi değerleri kullanıcının kültüründe görüntülemek için gereken genelleştirme kaynaklarını taşır. Uygulama yerelleştirme gerektirmiyorsa, uygulamayı kültürü temel alan en-US sabit kültürü destekleyecek şekilde yapılandırabilirsiniz.