Conserver les relations entre éléments, composants et modèles dans ASP.NET Core Blazor
Remarque
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.
Avertissement
Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la Stratégie de prise en charge de .NET et .NET Core. Pour la version actuelle, consultez la version .NET 8 de cet article.
Important
Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.
Pour la version actuelle, consultez la version .NET 8 de cet article.
Cet article explique comment utiliser l’attribut de directive @key
, pour la conservation des relations d’élément, de composant et de modèle, lors du rendu et lorsque les éléments ou composants changent par la suite.
Utilisation de l’attribut de directive @key
Si, après le rendu d’une liste d’éléments ou de composants, les éléments ou composants changent, Blazor doit décider lesquels des éléments ou composants précédents peuvent être conservés et déterminer le mode de mappage entre les objets de modèle et ces éléments ou composants. Normalement, ce processus est automatique et suffisant pour le rendu général, mais il existe souvent des cas où le contrôle du processus à l’aide de l’attribut @key
de directive est nécessaire.
Considérez l’exemple suivant qui illustre un problème de mappage de collection résolu à l’aide de @key
.
Pour les composants suivants :
- Le composant
Details
reçoit du composant parent des données (Data
), qui sont affichées dans un élément<input>
. Tout élément<input>
affiché donné peut recevoir le focus de la page de l’utilisateur lorsqu’il sélectionne l’un des éléments<input>
. - Le composant parent crée une liste d’objets de personne à afficher avec le composant
Details
. Toutes les trois secondes, une nouvelle personne est ajoutée à la collection.
Cette démonstration vous permet de :
- Sélectionner un
<input>
parmi plusieurs composantsDetails
rendus. - Étudier le comportement du focus de la page à mesure que la collection de personnes augmente automatiquement.
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; }
}
Dans le composant parent suivant, chaque itération d’ajout d’une personne dans OnTimerCallback
oblige Blazor à reconstruire l’intégralité de la collection. Le focus de la page restant sur la même position d’index des éléments <input>
, le focus change chaque fois qu’une personne est ajoutée. Il n’est pas souhaitable de déplacer le focus hors de la sélection de l’utilisateur. Après avoir démontré le mauvais comportement avec le composant suivant, l’attribut de directive @key
est utilisé pour améliorer l’expérience de l’utilisateur.
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; }
}
}
Le contenu de la collection people
change quand des entrées sont insérées, supprimées ou réorganisées. La regénération du rendu peut entraîner des différences de comportement visibles. Par exemple, chaque fois qu’une personne est insérée dans la collection people
, le focus de l’utilisateur est perdu.
Le processus de mappage d’éléments ou de composants à une collection peut être contrôlé avec l’attribut de directive @key
. L’utilisation de @key
garantit la conservation d’éléments ou de composants en fonction de la valeur de la clé. Si le composant Details
dans l’exemple précédent est indexé sur l’élément person
, Blazor ignore le nouveau rendu des composants Details
qui n’ont pas changé.
Pour modifier le composant parent afin d’utiliser l’attribut de directive @key
avec la collection people
, mettez à jour l’élément <Details>
comme suit :
<Details @key="person" Data="@person.Data" />
Quand la collection people
change, l’association entre les instances de Details
et de person
est conservée. Quand Person
est inséré au début de la collection, une nouvelle instance de Details
est insérée à la position correspondante. Les autres instances sont inchangées. Le focus de l’utilisateur n’est donc pas perdu à mesure que des personnes sont ajoutées à la collection.
D’autres mises à jour de collection présentent le même comportement quand l’attribut de directive @key
est utilisé :
- Si une instance est supprimée de la collection, seule l’instance de composant correspondante est supprimée de l’interface utilisateur. Les autres instances sont inchangées.
- Si les entrées de collection sont réorganisées, les instances de composant correspondantes sont conservées et réorganisées dans l’interface utilisateur.
Important
Les clés sont locales à chaque composant ou élément conteneur. Les clés ne sont pas comparées globalement dans le document.
Quand utiliser @key
En règle générale, il est judicieux d’utiliser @key
chaque fois qu’une liste est rendue (par exemple, dans un bloc foreach
) et qu’une valeur appropriée existe pour définir @key
.
Vous pouvez également utiliser @key
pour conserver une sous-arborescence d’éléments ou de composants quand un objet ne change pas, comme le montrent les exemples suivants.
Exemple 1 :
<li @key="person">
<input value="@person.Data" />
</li>
Exemple 2 :
<div @key="person">
@* other HTML elements *@
</div>
Si une instance de person
change, la directive d’attribut @key
force Blazor à :
- Ignorer l’intégralité de
<li>
ou de<div>
et les descendants. - Regénérer la sous-arborescence au sein de l’interface utilisateur avec de nouveaux éléments et composants.
Cela permet de garantir qu’aucun état de l’interface utilisateur n’est préservé quand la collection change dans une sous-arborescence.
Étendue de @key
La directive d’attribut @key
est délimitée à ses propres frères au sein de son parent.
Prenons l'exemple suivant. Les clés first
et second
sont comparées l’une à l’autre dans la même étendue de l’élément <div>
externe :
<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>
L’exemple suivant illustre les clés first
et second
dans leurs propres étendues, sans aucun rapport entre elles et sans aucune influence de l’une sur l’autre. Chaque étendue @key
s’applique uniquement à son élément <div>
parent, et non à travers les éléments <div>
parents :
<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
Pour le composant Details
présenté précédemment, les exemples suivants génèrent le rendu des données person
dans la même étendue @key
et illustrent des cas d’usage standard pour @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>
Les exemples suivants étendent uniquement @key
à l’élément <div>
ou <li>
qui entoure chaque instance de composant Details
. Les données person
pour chaque membre de la collection people
ne sont donc pas indexées sur chaque instance de person
à travers les composants Details
rendus. Évitez les modèles suivants quand vous utilisez @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>
Quand ne pas utiliser @key
Le rendu avec @key
entraîne un coût en termes de performances. Ce coût en termes de performances n’est pas important, mais spécifiez uniquement @key
si la conservation de l’élément ou du composant profite à l’application.
Même si @key
n’est pas utilisé, Blazor conserve autant que possible les instances des composants et des éléments enfants. Le seul avantage à utiliser @key
est de contrôler la façon dont les instances de modèle sont mappées aux instances de composant conservées afin d’éviter que Blazor ne sélectionne le mappage.
Valeurs à utiliser pour @key
En règle générale, il est judicieux de fournir l’une des valeurs suivantes pour @key
:
- Instances d’objet de modèle. Par exemple, l’instance
Person
(person
) a été utilisée dans l’exemple précédent. Cela garantit une conservation basée sur l’égalité des références d’objet. - Identificateurs uniques. Par exemple, des identificateurs uniques peuvent être basés sur des valeurs de clé primaire de type
int
,string
ouGuid
.
Vérifiez que les valeurs utilisées pour @key
ne sont pas en conflit. Si des valeurs en conflit sont détectées dans le même élément parent, Blazor lève une exception car il ne peut pas mapper de manière déterministe les anciens éléments ou composants aux nouveaux. Utilisez uniquement des valeurs distinctes, comme des instances d’objet ou des valeurs de clé primaire.