Pertahankan hubungan elemen, komponen, dan model di ASP.NET Core Blazor
Catatan
Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 9 dari artikel ini.
Peringatan
Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. 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 9 dari artikel ini.
Artikel ini menjelaskan cara menggunakan @key
atribut direktif untuk mempertahankan elemen, komponen, dan hubungan model saat penyajian dan elemen atau komponen kemudian berubah.
Penggunaan atribut direktif @key
Saat merender daftar elemen atau komponen dan elemen atau komponen yang kemudian berubah, Blazor harus memutuskan elemen atau komponen mana yang dipertahankan sebelumnya dan bagaimana objek model harus memetakannya. Biasanya, proses ini otomatis dan cukup untuk penyajian umum, tetapi sering ada kasus di mana mengontrol proses menggunakan atribut direktif @key
diperlukan.
Pertimbangkan contoh berikut yang menunjukkan masalah pemetaan koleksi yang diselesaikan dengan menggunakan @key
.
Untuk komponen berikut:
- Komponen
Details
menerima data (Data
) dari komponen induk, yang ditampilkan dalam<input>
elemen. Setiap elemen<input>
yang ditampilkan dapat menerima fokus halaman dari pengguna saat mereka memilih salah satu elemen<input>
. - Komponen induk membuat daftar objek orang untuk ditampilkan menggunakan
Details
komponen. Setiap tiga detik, orang baru ditambahkan ke koleksi.
Demonstrasi ini memungkinkan Anda untuk:
- Memilih
<input>
dari beberapa komponenDetails
yang dirender. - Mempelajari perilaku fokus halaman saat koleksi orang bertambah secara otomatis.
Details.razor
:
<input value="@Data" />
@code {
[Parameter]
public string? Data { get; set; }
}
<input value="@Data" />
@code {
[Parameter]
public string? Data { get; set; }
}
<input value="@Data" />
@code {
[Parameter]
public string? Data { get; set; }
}
<input value="@Data" />
@code {
[Parameter]
public string? Data { get; set; }
}
<input value="@Data" />
@code {
[Parameter]
public string Data { get; set; }
}
<input value="@Data" />
@code {
[Parameter]
public string Data { get; set; }
}
Dalam komponen induk berikut, setiap iterasi penambahan seseorang menghasilkan OnTimerCallback
Blazor pembangunan kembali seluruh koleksi. Fokus halaman tetap pada posisi indeks yang sama dari elemen <input>
, sehingga fokus bergeser setiap kali ada orang yang ditambahkan. Menggeser fokus dari apa yang dipilih pengguna bukanlah perilaku yang diinginkan. Setelah menunjukkan perilaku buruk dengan komponen berikut, atribut arahan @key
digunakan untuk meningkatkan pengalaman pengguna.
People.razor
:
@page "/people"
@using System.Timers
@implements IDisposable
<PageTitle>People</PageTitle>
<h1>People Example</h1>
@foreach (var person in people)
{
<Details Data="@person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string? Data { get; set; }
}
}
People.razor
:
@page "/people"
@using System.Timers
@implements IDisposable
<PageTitle>People</PageTitle>
<h1>People Example</h1>
@foreach (var person in people)
{
<Details Data="@person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string? Data { get; set; }
}
}
PeopleExample.razor
:
@page "/people-example"
@using System.Timers
@implements IDisposable
@foreach (var person in people)
{
<Details Data="person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string? Data { get; set; }
}
}
PeopleExample.razor
:
@page "/people-example"
@using System.Timers
@implements IDisposable
@foreach (var person in people)
{
<Details Data="person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string? Data { get; set; }
}
}
PeopleExample.razor
:
@page "/people-example"
@using System.Timers
@implements IDisposable
@foreach (var person in people)
{
<Details Data="person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string Data { get; set; }
}
}
PeopleExample.razor
:
@page "/people-example"
@using System.Timers
@implements IDisposable
@foreach (var person in people)
{
<Details Data="person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new List<Person>()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string Data { get; set; }
}
}
Konten kumpulan people
berubah dengan entri yang disisipkan, dihapus, atau diurutkan ulang. Perenderan ulang dapat menyebabkan perbedaan perilaku yang terlihat. Misalnya, setiap kali seseorang dimasukkan ke dalam people
koleksi, fokus pengguna hilang.
Proses pemetaan elemen atau komponen ke koleksi dapat dikontrol dengan atribut arahan @key
. Penggunaan @key
menjamin preservasi elemen atau komponen berdasarkan nilai kunci. Jika komponen Details
dalam contoh sebelumnya dikunci pada item person
, Blazor mengabaikan perenderan ulang komponen Details
yang tidak berubah.
Untuk memodifikasi komponen induk untuk menggunakan atribut direktif @key
dengan people
koleksi, perbarui <Details>
elemen ke yang berikut:
<Details @key="person" Data="@person.Data" />
Saat koleksi people
berubah, hubungan antara instans Details
dan instans person
dipertahankan. Saat Person
disisipkan di awal koleksi, satu instans Details
baru disisipkan pada posisi yang sesuai. Instans lain dibiarkan tidak berubah. Oleh karena itu, fokus pengguna tidak hilang saat orang ditambahkan ke koleksi.
Pembaruan koleksi lainnya menunjukkan perilaku yang sama ketika atribut arahan @key
digunakan:
- Jika sebuah instans dihapus dari koleksi, hanya instans komponen terkait yang dihapus dari antarmuka pengguna. Instans lain dibiarkan tidak berubah.
- Jika entri koleksi diurutkan ulang, instans komponen terkait akan dipertahankan dan diurutkan ulang di antarmuka pengguna.
Penting
Kunci bersifat lokal untuk setiap elemen atau komponen kontainer. Kunci tidak dibandingkan secara global di seluruh dokumen.
Kapan harus menggunakan @key
Biasanya, masuk akal untuk menggunakan @key
setiap kali daftar dirender (misalnya, dalam blok foreach
) dan ada nilai yang sesuai untuk mendefinisikan @key
.
Anda juga dapat menggunakan @key
untuk mempertahankan elemen atau komponen sub-pohon saat objek tidak berubah, seperti yang ditunjukkan contoh berikut.
Contoh 1:
<li @key="person">
<input value="@person.Data" />
</li>
Contoh 2:
<div @key="person">
@* other HTML elements *@
</div>
Jika instans person
berubah, arahan atribut @key
memaksa Blazor untuk:
- Membuang seluruh
<li>
atau<div>
dan keturunannya. - Membangun kembali sub-pohon di dalam antarmuka pengguna dengan elemen dan komponen baru.
Ini berguna untuk menjamin bahwa tidak ada status antarmuka pengguna yang dipertahankan saat koleksi berubah dalam sub-pohon.
Cakupan @key
Arahan atribut @key
dicakupkan ke saudaranya sendiri di dalam induknya.
Pertimbangkan contoh berikut. Kunci first
dan second
dibandingkan dengan satu sama lain dalam cakupan elemen <div>
luar yang sama:
<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>
Contoh berikut menunjukkan kunci first
dan second
dalam cakupannya masing-masing, yang tidak terkait dengan satu sama lain dan tidak memiliki pengaruh terhadap satu sama lain. Setiap cakupan @key
hanya berlaku untuk elemen <div>
induknya, bukan di seluruh elemen <div>
induknya:
<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
Untuk komponen Details
yang ditampilkan sebelumnya, contoh berikut merender data person
dalam cakupan @key
yang sama dan menunjukkan kasus penggunaan umum untuk @key
:
<div>
@foreach (var person in people)
{
<Details @key="person" Data="@person.Data" />
}
</div>
@foreach (var person in people)
{
<div @key="person">
<Details Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li @key="person">
<Details Data="@person.Data" />
</li>
}
</ol>
Contoh berikut hanya mencakup @key
ke elemen <div>
atau <li>
yang mengelilingi setiap instans komponen Details
. Oleh karena itu, data person
untuk setiap anggota koleksi people
tidak dikunci pada setiap instans person
di seluruh komponen Details
yang dirender. Hindari pola berikut saat menggunakan @key
:
@foreach (var person in people)
{
<div>
<Details @key="person" Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li>
<Details @key="person" Data="@person.Data" />
</li>
}
</ol>
Kapan tidak seharusnya menggunakan @key
Ada biaya performa saat merender dengan @key
. Biaya performa tidak besar, tetapi hanya tentukan @key
jika mempertahankan elemen atau komponen menguntungkan aplikasi.
Meskipun @key
tidak digunakan, Blazor mempertahankan instans elemen dan komponen turunan sebanyak mungkin. Satu-satunya keuntungan menggunakan @key
adalah kontrol atas bagaimana instans model dipetakan ke instans komponen yang dipertahankan, alih-alih Blazor yang memilih pemetaan.
Nilai yang akan digunakan untuk @key
Secara umum, masuk akal untuk memberikan salah satu nilai berikut untuk @key
:
- Instans objek model. Misalnya, instans
Person
(person
) digunakan pada contoh sebelumnya. Ini memastikan preservasi berdasarkan kesetaraan referensi objek. - Pengidentifikasi unik. Misalnya, pengidentifikasi unik dapat didasarkan pada nilai kunci primer dari jenis
int
,string
, atauGuid
.
Pastikan bahwa nilai yang digunakan untuk @key
tidak bentrok. Jika terdeteksi nilai yang bentrok dalam elemen induk yang sama, Blazor akan menampilkan pengecualian karena tidak dapat secara deterministik memetakan elemen atau komponen lama ke elemen atau komponen baru. Hanya gunakan nilai yang berbeda, seperti instans objek atau nilai kunci primer.
ASP.NET Core