Zachování relací prvků, komponent a modelů v ASP.NET Core Blazor
Poznámka:
Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Upozorňující
Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v tématu .NET a .NET Core Zásady podpory. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Důležité
Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.
Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Tento článek vysvětluje, jak pomocí atributu @key
direktiv zachovat relace elementu, komponenty a modelu při vykreslování a následné změně prvků nebo komponent.
Použití atributu direktivy @key
Při vykreslování seznamu prvků nebo součástí a prvků nebo součástí se následně musí rozhodnout, Blazor které z předchozích prvků nebo součástí jsou zachovány a jak se mají objekty modelu mapovat na ně. Obvykle je tento proces automatický a dostatečný pro obecné vykreslování, ale často existují případy, kdy řízení procesu pomocí atributu @key
direktivy vyžaduje.
Podívejte se na následující příklad, který ukazuje problém mapování kolekce, který je vyřešen pomocí @key
.
Pro následující komponenty:
- Komponenta
Details
přijímá data (Data
) z nadřazené komponenty, která se zobrazí v elementu<input>
. Jednotlivé zobrazené elementy<input>
můžou získat fokus stránky, když uživatel vybere některý z elementů<input>
. - Nadřazená komponenta vytvoří seznam objektů osob pro zobrazení pomocí
Details
komponenty. Každé tři sekundy se do kolekce přidá nová osoba.
Tato ukázka umožňuje:
- Vybrat element
<input>
z několika vykreslených komponentDetails
. - Zkoumat chování fokusu stránky s tím, jak se kolekce osob automaticky zvětšuje.
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; }
}
Každá iterace přidání osoby v OnTimerCallback
následující nadřazené komponentě vede k Blazor opětovnému sestavení celé kolekce. Fokus stránky zůstane v elementech <input>
na pozici se stejným indexem, takže se při každém přidání uživatele posune. Posun fokusu mimo uživatelem vybraný element není žádoucí. Po ukázce špatného chování na následující komponentě se k vylepšení uživatelského prostředí používá atribut direktivy @key
.
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; }
}
}
Obsah kolekce people
se změní při vložení, odstranění nebo změně pořadí položek. Opětovné vykreslování může mít za následek viditelné změny chování. Například při každém vložení osoby do people
kolekce dojde ke ztrátě fokusu uživatele.
Proces mapování elementů nebo komponent na kolekci je možné řídit pomocí atributu direktivy @key
. Použitím atributu @key
se zajistí zachování elementů nebo komponent na základě hodnoty klíče. Pokud komponenta Details
ve výše uvedeném příkladu jako klíč používá položku person
, Blazor ignoruje vykreslování komponent Details
, které se nezměnily.
Chcete-li upravit nadřazenou komponentu @key
tak, aby používala atribut direktivy people
s kolekcí, aktualizujte <Details>
prvek na následující:
<Details @key="person" Data="@person.Data" />
Při změně kolekce people
se zachová přidružení mezi instancemi Details
a instancemi person
. Když se na začátek kolekce vloží objekt Person
, na odpovídající pozici se vloží jedna nová instance Details
. Ostatní instance zůstanou beze změny. Proto se při přidávání osob do kolekce neztratí fokus uživatele.
Stejné chování při použití atributu direktivy @key
vykazují i ostatní aktualizace kolekcí:
- Pokud se z kolekce odstraní instance, z uživatelského rozhraní se odebere pouze odpovídající instance komponenty. Ostatní instance zůstanou beze změny.
- Pokud dojde ke změně pořadí položek kolekce, v uživatelském rozhraní se zachovají odpovídající instance komponenty a změní se jejich pořadí.
Důležité
Klíče jsou lokální pro každý element nebo každou komponentu kontejneru. Klíče se neporovnávají globálně napříč dokumentem.
Kdy použít atribut @key
Obvykle dává smysl použít atribut @key
vždy, když se vykresluje seznam (například v bloku foreach
) a pro definování atributu @key
existuje vhodná hodnota.
Atribut @key
můžete použít také k zachování podstromu elementů nebo komponent, když nedojde ke změně objektu, jak ukazují následující příklady.
Příklad 1:
<li @key="person">
<input value="@person.Data" />
</li>
Příklad 2:
<div @key="person">
@* other HTML elements *@
</div>
Pokud dojde ke změně instance person
, atribut direktivy @key
přinutí Blazor, aby provedl následující:
- Zahodil celý element
<li>
nebo<div>
a jejich potomky. - Znovu sestavil podstrom v rámci uživatelského rozhraní s novými elementy a komponentami.
To je užitečné k zajištění, aby se při změně kolekce v rámci podstromu nezachoval žádný stav uživatelského rozhraní.
Obor atributu @key
Atribut direktivy @key
má obor vymezený na podřízené elementy nadřazeného elementu na stejné úrovni.
Představte si následující příklad. Klíče first
a second
se porovnávají mezi sebou ve stejném oboru vnějšího elementu <div>
:
<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>
Následující příklad ukazuje klíče first
a second
ve vlastních oborech, které spolu nesouvisí a vzájemně se neovlivňují. Obor každého atributu @key
se vztahuje na vlastní nadřazený element <div>
, nikoli na ostatní nadřazené elementy <div>
:
<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
V případě výše uvedené komponenty Details
následující příklady vykreslí data person
ve stejném oboru @key
a ukazují obvyklé případy použití atributu @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>
V následujících příkladech je obor atributu @key
vymezený pouze na element <div>
nebo <li>
kolem každé instance komponenty Details
. Proto se pro data person
každého člena kolekce people
nepoužívá jako klíč každá instance person
napříč vykreslenými komponentami Details
. Při používání atributu @key
se vyhněte následujícím vzorům:
@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>
Kdy nepoužívat atribut @key
Vykreslování s použitím atributu @key
má dopad na výkon. Dopad na výkon není velký, ale přesto používejte atribut @key
pouze v případě, že je zachování prvku nebo komponenty pro aplikaci přínosné.
I když nepoužijete atribut @key
, Blazor zachovává instance podřízených elementů a komponent v co největší míře. Jedinou výhodou použití atributu @key
je to, že máte kontrolu nad tím, jak se instance modelu mapují na zachované instance komponent, namísto toho, aby mapování vybírala architektura Blazor.
Jaké hodnoty použít pro atribut @key
Obecně je vhodné pro atribut @key
zadat některou z následujících hodnot:
- Instance objektů modelu. Například ve výše uvedeném příkladu se použila instance objektu
Person
(person
). Tím se zajistí zachování na základě rovnosti odkazů na objekty. - Jedinečné identifikátory. Jedinečné identifikátory můžou vycházet například z hodnot primárního klíče typu
int
,string
neboGuid
.
Ujistěte se, že hodnoty použité pro atribut @key
nejsou v konfliktu. Pokud se v rámci stejného nadřazeného elementu zjistí kolidující hodnoty, Blazor vyvolá výjimku, protože nedokáže deterministicky mapovat staré elementy nebo komponenty na nové elementy nebo komponenty. Používejte pouze jedinečné hodnoty, například instance objektů nebo hodnoty primárního klíče.