Bagikan melalui


penyajian komponen ASP.NET Core Razor

Catatan

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

Penting

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

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

Artikel ini menjelaskan Razor penyajian komponen di aplikasi ASP.NET Core Blazor , termasuk kapan harus memanggil StateHasChanged untuk memicu komponen secara manual untuk dirender.

Konvensi penyajian untuk ComponentBase

Komponen harus dirender saat pertama kali ditambahkan ke hierarki komponen oleh komponen induk. Ini adalah satu-satunya waktu komponen harus dirender. Komponen dapat dirender di lain waktu sesuai dengan logika dan konvensi mereka sendiri.

Secara default, Razor komponen mewarisi dari ComponentBase kelas dasar, yang berisi logika untuk memicu penyajian pada waktu berikut:

Komponen yang diwarisi dari ComponentBase melewati rerender karena pembaruan parameter jika salah satu dari berikut ini benar:

Mengontrol alur penyajian

Dalam kebanyakan kasus, ComponentBase konvensi menghasilkan subset rerender komponen yang benar setelah peristiwa terjadi. Pengembang biasanya tidak diharuskan untuk menyediakan logika manual untuk memberi tahu kerangka kerja komponen mana yang akan dirender dan kapan harus merendernya. Efek keseluruhan dari konvensi kerangka kerja adalah bahwa komponen yang menerima peristiwa merender sendiri, yang secara rekursif memicu penyajian komponen turunan yang nilai parameternya mungkin telah berubah.

Untuk informasi selengkapnya tentang implikasi performa konvensi kerangka kerja dan cara mengoptimalkan hierarki komponen aplikasi untuk penyajian, lihat praktik terbaik performa inti Blazor ASP.NET.

Penyajian streaming

Gunakan penyajian streaming dengan penyajian sisi server statis (SSR statis) atau pra-penyajian untuk mengalirkan pembaruan konten pada aliran respons dan meningkatkan pengalaman pengguna untuk komponen yang melakukan tugas asinkron yang berjalan lama untuk sepenuhnya dirender.

Misalnya, pertimbangkan komponen yang membuat kueri database atau panggilan API web yang berjalan lama untuk merender data saat halaman dimuat. Biasanya, tugas asinkron yang dijalankan sebagai bagian dari penyajian komponen sisi server harus diselesaikan sebelum respons yang dirender dikirim, yang dapat menunda pemuatan halaman. Setiap keterlambatan yang signifikan dalam merender halaman membahayakan pengalaman pengguna. Untuk meningkatkan pengalaman pengguna, penyajian streaming awalnya merender seluruh halaman dengan cepat dengan konten tempat penampung saat operasi asinkron dijalankan. Setelah operasi selesai, konten yang diperbarui dikirim ke klien pada koneksi respons yang sama dan ditambal ke DOM.

Penyajian streaming mengharuskan server untuk menghindari buffer output. Data respons harus mengalir ke klien saat data dihasilkan. Untuk host yang memberlakukan buffering, penyajian streaming terdegradasi dengan baik, dan halaman dimuat tanpa penyajian streaming.

Untuk melakukan streaming pembaruan konten saat menggunakan penyajian sisi server statis (SSR statis) atau pra-penyajian, terapkan [StreamRendering(true)] atribut ke komponen. Penyajian streaming harus diaktifkan secara eksplisit karena pembaruan yang dialirkan dapat menyebabkan konten pada halaman bergeser. Komponen tanpa atribut secara otomatis mengadopsi penyajian streaming jika komponen induk menggunakan fitur tersebut. Teruskan false ke atribut dalam komponen anak untuk menonaktifkan fitur pada saat itu dan turun lebih jauh ke subtree komponen. Atribut ini fungsional saat diterapkan ke komponen yang Razor disediakan oleh pustaka kelas.

Contoh berikut didasarkan pada Weather komponen dalam aplikasi yang dibuat dari Blazor templat proyek Aplikasi Web. Panggilan untuk Task.Delay mensimulasikan pengambilan data cuaca secara asinkron. Komponen awalnya merender konten tempat penampung ("Loading...") tanpa menunggu penundaan asinkron selesai. Ketika penundaan asinkron selesai dan konten data cuaca dihasilkan, konten dialirkan ke respons dan di-patch ke dalam tabel prakiraan cuaca.

Weather.razor:

@page "/weather"
@attribute [StreamRendering(true)]

...

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        ...
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    ...

    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        await Task.Delay(500);

        ...

        forecasts = ...
    }
}

Menekan refresh UI (ShouldRender)

ShouldRender dipanggil setiap kali komponen dirender. Ambil alih ShouldRender untuk mengelola penyegaran UI. Jika implementasi mengembalikan true, UI di-refresh.

Bahkan jika ShouldRender ditimpa, komponen selalu awalnya dirender.

ControlRender.razor:

@page "/control-render"

<PageTitle>Control Render</PageTitle>

<h1>Control Render Example</h1>

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

Untuk informasi selengkapnya tentang praktik terbaik performa yang berkaitan dengan ShouldRender, lihat praktik terbaik performa inti Blazor ASP.NET.

Kapan harus memanggil StateHasChanged

Panggilan StateHasChanged memungkinkan Anda untuk memicu render kapan saja. Namun, berhati-hatilah untuk tidak memanggil StateHasChanged secara tidak perlu, yang merupakan kesalahan umum yang memberlakukan biaya penyajian yang tidak perlu.

Kode seharusnya tidak perlu memanggil StateHasChanged saat:

  • Secara rutin menangani peristiwa, baik secara sinkron atau asinkron, karena ComponentBase memicu render untuk sebagian besar penanganan aktivitas rutin.
  • Menerapkan logika siklus hidup yang khas, seperti OnInitialized atau OnParametersSetAsync, baik secara sinkron atau asinkron, karena ComponentBase memicu render untuk peristiwa siklus hidup yang khas.

Namun, mungkin masuk akal untuk memanggil StateHasChanged dalam kasus yang dijelaskan di bagian berikut dari artikel ini:

Handler asinkron melibatkan beberapa fase asinkron

Karena cara tugas didefinisikan dalam .NET, penerima Task hanya dapat mengamati penyelesaian akhirnya, bukan status asinkron menengah. Oleh karena itu, ComponentBase hanya dapat memicu penyajian kembali ketika Task pertama kali dikembalikan dan ketika akhirnya Task selesai. Kerangka kerja tidak dapat mengetahui untuk merender komponen di titik perantara lainnya, seperti ketika IAsyncEnumerable<T>mengembalikan data dalam serangkaian perantaraTask. Jika Anda ingin merender di titik perantara, hubungi StateHasChanged di titik-titik tersebut.

Pertimbangkan komponen berikut CounterState1 , yang memperbarui hitungan empat kali setiap kali IncrementCount metode dijalankan:

  • Render otomatis terjadi setelah kenaikan pertama dan terakhir dari currentCount.
  • Render manual dipicu oleh panggilan ke StateHasChanged ketika kerangka kerja tidak secara otomatis memicu rerender di titik pemrosesan perantara di mana currentCount bertambah bertahap.

CounterState1.razor:

@page "/counter-state-1"

<PageTitle>Counter State 1</PageTitle>

<h1>Counter State Example 1</h1>

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}

Menerima panggilan dari sesuatu yang eksternal ke sistem penyajian Blazor dan penanganan peristiwa

ComponentBase hanya tahu tentang metode siklus hidupnya sendiri dan Blazorperistiwa yang dipicu. ComponentBase tidak tahu tentang peristiwa lain yang mungkin terjadi dalam kode. Misalnya, setiap peristiwa C# yang dibesarkan oleh penyimpanan data kustom tidak diketahui Blazoroleh . Agar peristiwa tersebut memicu penyajian untuk menampilkan nilai yang diperbarui di UI, panggil StateHasChanged.

Pertimbangkan komponen berikut CounterState2 yang menggunakan System.Timers.Timer untuk memperbarui hitungan secara berkala dan panggilan StateHasChanged untuk memperbarui UI:

  • OnTimerCallback berjalan di luar alur penyajian terkelola Blazoratau pemberitahuan peristiwa. Oleh karena itu, OnTimerCallback harus memanggil StateHasChanged karena Blazor tidak mengetahui perubahan currentCount pada panggilan balik.
  • Komponen mengimplementasikan IDisposable, di mana Timer dibuang ketika kerangka kerja memanggil Dispose metode . Untuk informasi lebih lanjut, lihat siklus hidup komponen Razor ASP.NET Core.

Karena panggilan balik dipanggil di luar Blazorkonteks sinkronisasi 's, komponen harus membungkus logika OnTimerCallback masuk ComponentBase.InvokeAsync untuk memindahkannya ke konteks sinkronisasi perender. Ini setara dengan marsekal utas UI dalam kerangka kerja UI lainnya. StateHasChanged hanya dapat dipanggil dari konteks sinkronisasi perender dan melemparkan pengecualian jika tidak:

System.InvalidOperationException: 'Utas saat ini tidak terkait dengan Dispatcher. Gunakan InvokeAsync() untuk mengalihkan eksekusi ke Dispatcher saat memicu penyajian atau status komponen.'

CounterState2.razor:

@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<PageTitle>Counter State 2</PageTitle>

<h1>Counter State Example 2</h1>

<p>
    This counter demonstrates <code>Timer</code> disposal.
</p>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new Timer(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

Untuk merender komponen di luar subtree yang dirender oleh peristiwa tertentu

UI mungkin melibatkan:

  1. Mengirimkan peristiwa ke satu komponen.
  2. Mengubah beberapa status.
  3. Merender komponen yang sama sekali berbeda yang bukan merupakan turunan dari komponen yang menerima peristiwa.

Salah satu cara untuk menangani skenario ini adalah dengan menyediakan kelas manajemen status, seringkali sebagai layanan injeksi dependensi (DI), disuntikkan ke beberapa komponen. Ketika satu komponen memanggil metode pada manajer status, manajer status menaikkan peristiwa C# yang kemudian diterima oleh komponen independen.

Untuk pendekatan mengelola status, lihat sumber daya berikut:

Untuk pendekatan manajer status, peristiwa C# berada di luar alur penyajian Blazor . Panggil StateHasChanged komponen lain yang ingin Anda render sebagai respons terhadap peristiwa manajer status.

Pendekatan manajer status mirip dengan kasus sebelumnya dengan System.Timers.Timer di bagian sebelumnya. Karena tumpukan panggilan eksekusi biasanya tetap pada konteks sinkronisasi perender, panggilan InvokeAsync biasanya tidak diperlukan. InvokeAsync Panggilan hanya diperlukan jika logika lolos dari konteks sinkronisasi, seperti memanggil ContinueWith atau Task menunggu Task dengan ConfigureAwait(false). Untuk informasi selengkapnya, lihat bagian Menerima panggilan dari sesuatu yang eksternal ke sistem penyajian Blazor dan penanganan peristiwa.

Indikator kemajuan pemuatan WebAssembly untuk Blazor Web Apps

Indikator kemajuan pemuatan tidak ada di aplikasi yang dibuat dari Blazor templat proyek Aplikasi Web. Fitur indikator kemajuan pemuatan baru direncanakan untuk rilis .NET di masa mendatang. Sementara itu, aplikasi dapat mengadopsi kode kustom untuk membuat indikator kemajuan pemuatan. Untuk informasi selengkapnya, lihat startup ASP.NET CoreBlazor.