Beibehalten von Element-, Komponenten- und Modellbeziehungen in ASP.NET Core Blazor
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
In diesem Artikel wird erläutert, wie Sie das @key
-Anweisungsattribut verwenden, um Element-, Komponenten- und Modellbeziehungen beim Rendern beizubehalten, wenn sich die Elemente oder Komponenten anschließend ändern.
Verwenden des @key
-Anweisungsattributs
Wenn Sie eine Element- oder Komponentenliste rendern und die Elemente oder Komponenten nachfolgend geändert werden, muss Blazor bestimmen, welche der vorherigen Elemente oder Komponenten beibehalten werden und wie Modellobjekte diesen zugeordnet werden sollen. Normalerweise ist dieser Prozess automatisch und für das allgemeine Rendering ausreichend, aber es gibt häufig Fälle, in denen die Steuerung des Prozesses mithilfe des Anweisungsattributs @key
erforderlich ist.
Sehen Sie sich das folgende Beispiel an, das ein Problem bei einer Sammlungszuordnung veranschaulicht, das mithilfe von @key
gelöst wird.
Für die folgenden Komponenten:
- Die
Details
-Komponente empfängt Daten (Data
) von der übergeordneten Komponente, die in einem<input>
-Element angezeigt wird. Jedes angezeigte<input>
-Element kann den Fokus der Seite vom Benutzer erhalten, wenn er eines der<input>
-Elemente auswählt. - Die übergeordnete Komponente erstellt mithilfe der
Details
-Komponente eine Liste anzuzeigender person-Objekte. Alle drei Sekunden wird der Sammlung eine neue Person hinzugefügt.
Diese Demonstration ermöglicht Ihnen Folgendes:
- Auswählen eines
<input>
aus mehreren gerendertenDetails
-Komponenten - Untersuchen des Verhaltens des Fokus der Seite, wenn die Größe der people-Sammlung automatisch zunimmt
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; }
}
In der folgenden übergeordneten Komponente führt jede Iteration für das Hinzufügen einer Person in OnTimerCallback
dazu, dass Blazor die gesamte Collection neu erstellt. Der Fokus der Seite bleibt auf derselben Indexposition der <input>
-Elemente, sodass sich der Fokus jedes Mal verschiebt, wenn eine Person hinzugefügt wird. Das Abweichen des Fokus von der Auswahl durch den Benutzer ist kein wünschenswertes Verhalten. Nachdem mit der folgenden Komponente das unzureichende Verhalten veranschaulicht wurde, wird das @key
-Anweisungsattribut verwendet, um die Benutzerfreundlichkeit zu verbessern.
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; }
}
}
Der Inhalt der people
-Sammlung ändert sich durch eingefügte, gelöschte oder neu sortierte Einträge. Erneutes Rendering kann zu sichtbaren Verhaltensunterschieden führen. Beispielsweise geht bei jedem Einfügen einer Person in die Sammlung people
der Bnutzerfokus verloren.
Das Zuordnen von Elementen oder Komponenten zu einer Sammlung kann mit dem @key
-Anweisungsattribut gesteuert werden. Die Verwendung von @key
garantiert, dass Elemente oder Komponenten basierend auf dem Wert von @key beibehalten werden. Wenn in der Details
-Komponente im vorherigen Beispiel @key auf person
festgelegt ist, erfolgt in Blazor kein erneutes Rendering von Details
-Komponenten, die sich nicht geändert haben.
Um für die übergeordnete Komponente das @key
-Anweisungsattribut mit der people
-Collection zu verwenden, aktualisieren Sie das <Details>
-Element wie folgt:
<Details @key="person" Data="@person.Data" />
Wenn sich die people
-Sammlung ändert, bleibt die Zuordnung zwischen Details
-Instanzen und person
-Instanzen erhalten. Wenn am Anfang der Sammlung eine Person
eingefügt wird, wird an der entsprechenden Position eine neue Details
-Instanz eingefügt. Andere Instanzen bleiben unverändert. Daher geht der Fokus des Benutzers nicht verloren, wenn der Sammlung Personen hinzugefügt werden.
Andere Sammlungsaktualisierungen weisen das gleiche Verhalten auf, wenn das @key
-Anweisungsattribut verwendet wird:
- Wenn eine Instanz aus der Sammlung gelöscht wird, wird nur die entsprechende Komponenteninstanz von der Benutzeroberfläche entfernt. Andere Instanzen bleiben unverändert.
- Wenn Sammmlungseinträge neu sortiert werden, werden die entsprechenden Komponenteninstanzen beibehalten und auf der Benutzeroberfläche neu angeordnet.
Wichtig
Schlüssel sind für jedes Containerelement oder jede Komponente lokal. Schlüssel werden nicht dokumentübergreifend global verglichen.
Empfohlene Verwendung von @key
In der Regel ist es sinnvoll, @key
zu verwenden, wenn eine Liste gerendert wird (z. B. in einem foreach
-Block) und ein geeigneter Wert vorhanden ist, um @key
zu definieren.
Sie können @key
auch verwenden, um eine Element- oder Komponentenunterstruktur zu bewahren, wenn sich ein Objekt nicht ändert, wie in den folgenden Beispielen gezeigt.
Beispiel 1:
<li @key="person">
<input value="@person.Data" />
</li>
Beispiel 2:
<div @key="person">
@* other HTML elements *@
</div>
Wenn sich eine person
-Instanz ändert, erzwingt das @key
-Anweisungsattribut in Blazor Folgendes:
- Verwerfen des gesamten
<li>
- oder<div>
-Elements und seiner Nachfolger - Neuerstellen der Unterstruktur innerhalb der Benutzeroberfläche mit neuen Elementen und Komponenten
Dies ist hilfreich, um zu gewährleisten, dass kein Benutzeroberflächenzustand beibehalten wird, wenn sich die Sammlung innerhalb einer Unterstruktur ändert.
Bereich von @key
Die @key
-Attributanweisung ist auf ihre eigenen gleichgeordneten Elemente innerhalb des übergeordneten Elements begrenzt.
Betrachten Sie das folgende Beispiel. Die Schlüssel first
und second
werden im selben Bereich des <div>
-Elements miteinander verglichen:
<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>
Im folgenden Beispiel werden die Schlüssel first
und second
in ihren eigenen Bereichen veranschaulicht, die unabhängig voneinander stehen und keinen Einfluss aufeinander haben. Jeder @key
-Bereich gilt nur für sein übergeordnetes <div>
-Element, nicht für die übergeordneten <div>
-Elemente:
<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
Für die zuvor gezeigte Details
-Komponente rendern die folgenden Beispiele person
-Daten innerhalb desselben @key
-Bereichs und veranschaulichen typische Anwendungsfälle für @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>
Die folgenden Beispiele beschränken sich nur auf den Bereich @key
für das <div>
- oder <li>
-Element, das jede Details
-Komponenteninstanz umschließt. Daher werden person
-Daten für jedes Member der people
-Sammlung nicht für jede person
-Instanz für die gerenderten Details
-Komponenten schlüsselgebunden. Vermeiden Sie die folgenden Muster bei Verwendung von @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>
Ungeeignete Fälle für @key
Beim Rendern mit @key
wird die Leistung beeinträchtigt. Die Beeinträchtigung der Leistung ist nicht erheblich. Sie sollten @key
jedoch nur angeben, wenn sich die Beibehaltung des Elements oder der Komponente positiv auf die App auswirken.
Auch wenn @key
nicht verwendet wird, behält Blazor die untergeordneten Element- und Komponenteninstanzen so weit wie möglich bei. Der einzige Vorteil bei der Verwendung von @key
besteht in der Kontrolle darüber, wie Modellinstanzen den beibehaltenen Komponenteninstanzen zugeordnet werden, anstatt die Zuordnung durch Blazor bestimmen zu lassen.
Zu verwendende Werte für @key
Im Allgemeinen ist es sinnvoll, für @key
folgende Arten von Werten bereitzustellen:
- Modellobjektinstanzen. Beispielsweise wurde im vorherigen Beispiel die
Person
-Instanz (person
) verwendet. Dadurch wird die Beibehaltung basierend auf der Objektverweisgleichheit sichergestellt. - Eindeutige Bezeichner. Eindeutige Bezeichner können beispielsweise auf Primärschlüsselwerten vom Typ
int
,string
oderGuid
basieren.
Stellen Sie sicher, dass die für @key
verwendeten Werte nicht kollidieren. Wenn innerhalb desselben übergeordneten Elements kollidierende Werte erkannt werden, löst Blazor eine Ausnahme aus, da alte Elemente oder Komponenten nicht deterministisch neuen Elementen oder Komponenten zugeordnet werden können. Verwenden Sie nur eindeutige Werte wie Objektinstanzen oder Primärschlüsselwerte.