Aracılığıyla paylaş


ASP.NET Core Blazor işleme performansı en iyi yöntemleri

Uyarı

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

Uyarı

ASP.NET Core'un bu sürümü artık desteklenmiyor. Daha fazla bilgi için .NET ve .NET Core Destek Politikası'na bakın. Geçerli sürüm için bu makalenin .NET 9 sürümüne bakın.

İş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 meydana geldiğinde, alt bileşenlerin alt ağaçlarının yeniden oluşturulmasını atlayarak bir üst bileşenin işleme maliyetinin çoğunu azaltabilirsiniz. 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. İlk yüklenen bileşen olan kök bileşenin 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 bir parametre değerleri kümesi alındıktan sonra, Blazor bileşenin yeniden yenilenip yenilmeyeceğine karar verir. Varsayılan davranış olarak ShouldRendertrue değerini döndürürse, bileşenler yeniden oluşturulur; bu davranış geçersiz kılınmadığı sürece geçerlidir. Parametre değerleri, örneğin değişebilir özellikteki nesnelerse, değişmiş olabilir.

Önceki dizinin son iki adımı, bileşen hiyerarşisinin alt basamaklarına doğru özyinelemeli olarak ilerler. Çoğu durumda alt ağacın tamamı yeniden oluşturulur. Üst düzey bileşenleri hedefleyen olaylar, her bir alt bileşenin yeniden çizilmesi gerektiğinden maliyetli yeniden çizimlere yol açabilir.

Belirli bir alt ağaçta görselleştirme özyinelemesini önlemek için aşağıdaki yaklaşımlardan birini kullanın.

  • Alt bileşen parametrelerinin, string, int, bool ve DateTime gibi belirli sabit türlerde olduğundan emin olun. Sabit parametre değerleri değişmediyse, değişiklikleri algılamaya yönelik yerleşik mantık otomatik olarak yeniden boyutlandırmayı atlar. Eğer bir alt bileşeni <Customer CustomerId="item.CustomerId" /> ile işlerseniz ve buradaki CustomerId bir int türündeyse, Customer değişmediği sürece item.CustomerId bileşeni yeniden işlenmez.
  • Geçersiz kıl ShouldRender ve false döndür.
    • Parametreler ilkel olmayan türler veya desteklenmeyen değişmez türler olduğunda, karmaşık özel model türleri veya RenderFragment değerleri gibi, ve parametre değerleri değişmediğinde,
    • İ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.

† Daha fazla bilgi için, 'nin başvuru kaynağındaki Blazor(ChangeDetection.cs) değişiklik algılama mantığına bakın.

Uyarı

.NET başvuru kaynağına yönelik belge bağlantıları genellikle deponun varsayılan dalını yükler ve bu dal .NET'in sonraki sürümü için geçerli geliştirmeyi temsil eder. Belirli bir sürümün etiketini seçmek için Dallar veya etiketler arasında geçiş yap açılır listesini kullanın. Daha fazla bilgi için ASP.NET Core kaynak kodu için bir sürüm etiketi nasıl seçilir (dotnet/AspNetCore.Docs #26205) bölümüne bakın.

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 OnParametersSet ayarlandığında uçuş kimliklerinden biri değişirse, shouldRendertrue ayarlandığı için bileşen yeniden oluşturulur. Uçuş tanımlayıcıları denetlendikten sonra shouldRender ifadesi false olarak değerlendirilirse, pahalı bir yeniden oluşturma işleminden 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;
}

Bir olay işleyicisi ayrıca shouldRender öğesini true olarak ayarlayabilir. Çoğu bileşen için, tek tek olay işleyicileri düzeyinde rerendering belirlemek genellikle gerekli değildir.

Daha fazla bilgi 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.

Blazor, rastgele genişliğe sahip bir listenin görünümünü ve kaydırma davranışlarını oluşturmak için Virtualize<TItem> bileşenini sağlar; yalnızca geçerli kayan görünüm alanı içindeki liste öğelerini işler. Ö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, @page yönergesine sahip 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 aynı anda yalnızca bir kez görünür ve yalnızca bir kullanıcı hareketine yanıt olarak yeniden işlenir. 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ırdan veya binlerce hücreden oluşan kılavuzlar.
  • Milyonlarca veri noktası içeren dağılım grafikleri.

Her öğeyi, hücreyi veya veri noktasını ayrı bir bileşen örneği olarak modelleme durumunda, çoğu zaman sayıca o kadar fazladır ki bunların işleme performansı kritik olur. 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ümlerinin yeniden çizilmesini sağlayabilirsiniz. Bir tablodaki her satırda bir düğme bulunan birçok satır için, sayfanın veya tablonun tamamını yeniden çizmek yerine, sadece o tek satırı yeniden çizmek için bir alt bileşen kullanabilirsiniz. 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. Dağıtılan ASP.NET Core sunucu tarafı Blazor uygulamalarında belleği yönetme.

Alt bileşenleri üst bileşenlerin içine yerleştirin: 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ı bileşeni hesaba katmayı ChatMessageDisplay. Bunun yerine, alt bileşeni üst bileşenin içine alın. 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 dikkate alabilirsiniz. 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>;
}

Birden çok bileşen arasında kodun yeniden kullanılabilir olması için RenderTreeBuilder bildirin: RenderFragmentpublicstatic

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. RenderFragment temsilcisine atama yalnızca Razor bileşen dosyalarında (.razor) desteklenir.

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, ilkel parametreleri bir sınıfa paketlemenin her zaman bir avantaj olmadığını unutmayın. Parametre sayısını azaltabilir ancak değişiklik algılama ve işlemenin nasıl davranacağını da etkiler. İlkel olmayan parametrelerin geçirilmesi her zaman yeniden işlemeyi tetikler, çünkü Blazor rastgele nesnelerin dahili olarak değiştirilebilir duruma sahip olup olmadığını bilemezken, ilkel parametrelerin geçirilmesi yalnızca değerleri gerçekten değiştiyse yeniden işlemeyi tetikler.

Ayrıca, yukarıdaki örnekte gösterildiği gibi bir tablo hücresi bileşenine sahip olmamanın bir geliştirme olabileceğini ve bunun yerine mantığını üst bileşende yer alacak şekildedüşünün.

Uyarı

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:

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

IsFixed değerini true olarak ayarlamak, basamaklı değeri alan çok sayıda başka bileşen varsa performansı artırır. Mümkün olduğunda, basamaklı değerlere IsFixed öğesini true olarak ayarlayın. Sağlanan değer zaman içinde değişmediğinde, IsFixed değerini true olarak ayarlayabilirsiniz.

Bileşenin this basamaklı değer olarak geçtiği durumlarda, IsFixedtrueolarak da ayarlanabilir çünkü this bileşenin yaşam döngüsü boyunca hiçbir zaman değişmez:

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

Daha fazla bilgi için ASP.NET Core Blazor basamaklı değerler ve parametreler kısmına bakın.

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

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 öznitelik yayılımı ve rastgele parametreler.

SetParametersAsync manuel olarak uygulayın

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, SetParametersAsync geçersiz kılmak ve özel bir mantık sağlamak karmaşık ve zahmetlidir, bu nedenle bu yaklaşımın benimsenmesi genellikle ö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ı tetiklemeyin

Bazı tarayıcı olayları son derece sık meydana gelir. Örneğin, onmousemove ve onscroll saniyede on veya yüzlerce kez ateşleyebilirler. Ç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 render etmekten kaçının.

Bileşenler, ComponentBase'dan devralır ve bileşenin olay işleyicileri çağrıldıktan sonra otomatik olarak StateHasChanged çağrılı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 IHandleEvent arabiriminden yararlanarak Blazor'nin olay işleme davranışını denetleyebilir.

Uyarı

Bu bölümdeki yaklaşım, hata sınırlarına özel durumlar iletmez. Hata sınırlarını ComponentBase.DispatchExceptionAsync çağrısı yaparak destekleyen daha fazla bilgi ve örnek kod için bkz: AsNonRenderingEventHandler + ErrorBoundary = beklenmeyen davranış (dotnet/aspnetcore #54543).

Bir bileşenin olay işleyicilerinin tümü için yeniden render'ları önlemek amacıyla, IHandleEvent öğesini çağırmadan olay işleyicisiniz çalıştıran bir IHandleEvent.HandleEventAsync görev uygulayın ve StateHasChanged 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 yeniden render edilmeyi önlemeye ek olarak, aşağıdaki yardımcı yöntemi kullanarak tek bir olay işleyicisinden sonra yeniden render edilmeyi ö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);
    }
}

Bir çağrı EventUtil.AsNonRenderingEventHandler ile render tetiklemeyen bir olay işleyicisini çağırın.

Aşağıdaki örnekte:

  • HandleClick1 öğesini çağıran ilk düğmenin seçilmesi yeniden bir rendering işlemi başlatır.
  • HandleClick2 öğesini çağıran ikinci düğmeyi seçmek bir yeniden render tetiklemez.
  • HandleClick3 öğesini çağıran üçüncü düğmenin seçilmesi, bir rerender tetiklemez ve olay bağımsız değişkenlerini kullanır (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);
    }
}

IHandleEvent arabirimini uygulamaya ek olarak, bu makalede açıklanan diğer en iyi uygulamalardan yararlanarak olaylar işlendikten sonra istenmeyen işlemelerin azalmasına da yardımcı olabilirsiniz. Örneğin, hedef bileşenin alt bileşenlerinde ShouldRender'yi geçersiz kılmak, yeniden render etmeyi denetlemek için 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 oluşturulması düşük performansa yol açabilir.

Olay yönetimi makalesinde gösterilen aşağıdaki bileşen bir dizi düğme oluşturur. Her düğme, @onclick olayına bir temsilci atar; görüntülenecek çok sayıda 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 kullanarak çok sayıda düğmeyi işlemek için, aşağıdaki örnek, her düğmenin @onclick temsilcisini bir Action öğesine atayan bir düğme nesneleri koleksiyonu kullanı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 => { };
    }
}