Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Remarque
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 10 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 support .NET et .NET Core. Pour la version actuelle, consultez la version .NET 10 de cet article.
Optimisez la vitesse de rendu pour réduire la charge de travail de rendu et améliorer la réactivité de l’interface utilisateur, ce qui peut générer une amélioration de dix fois ou plus dans la vitesse de rendu de l’interface utilisateur.
Évitez de provoquer le rafraîchissement superflu des sous-arborescences de composants
Vous pourriez être en mesure de réduire la majeure partie des coûts de rendu d’un composant parent en évitant le nouveau rendu des sous-arborescences des composants enfants lorsqu'un événement se produit. Il convient de porter attention uniquement aux sous-arborescences dont le rendu s’avère particulièrement coûteux et susceptibles de ralentir l’interface utilisateur.
Au moment de l’exécution, les composants existent dans une hiérarchie. Un composant racine (le premier à être chargé) contient des composants enfants. Ces composants enfants possèdent eux-mêmes leurs propres composants enfants, et ainsi de suite. Lorsqu’un événement se produit, tel qu’un utilisateur sélectionnant un bouton, le processus suivant détermine les composants à réactiver :
- L’événement est acheminé vers le composant ayant généré le gestionnaire correspondant. Une fois le gestionnaire d’événement exécuté, le composant est rendu de nouveau.
- Lorsqu’un composant est rerendu, il fournit une nouvelle copie des valeurs des paramètres à chacun de ses composants enfants.
- Une fois qu’un nouvel ensemble de valeurs de paramètre est reçu, Blazor détermine s’il faut réorganiser le composant. Les composants sont réaffichés si
ShouldRenderretournetrue, conformément au comportement par défaut sauf redéfinition, et si les valeurs des paramètres ont pu évoluer, notamment lorsqu’il s’agit d’objets mutables.
Les deux dernières étapes de la séquence précédente continuent de façon récursive vers le bas de la hiérarchie des composants. Dans de nombreux cas, l’ensemble de la sous-arborescence est rendu à nouveau. Les événements visant des composants de haut niveau peuvent entraîner un réaffichage onéreux, puisque l’ensemble des composants situés en dessous doit alors être rendu à nouveau.
Pour empêcher la récursivité du rendu dans une sous-arborescence particulière, utilisez l’une des approches suivantes :
- Vérifiez que les paramètres de composant enfant sont de types immuables spécifiques†, tels que
string, ,intbooletDateTime. Le mécanisme intégré de détection des changements évite automatiquement le réaffichage lorsque les paramètres immuables demeurent inchangés. Si vous affichez un composant enfant avec<Customer CustomerId="item.CustomerId" />, oùCustomerIdest un typeint, alors le composantCustomern'est pas re-rendu sauf siitem.CustomerIdchange. - Remplacer
ShouldRender, renvoyerfalse:- Lorsque les paramètres sont des types nonprimitifs ou des types immuables non pris en charge†, tels que des types personnalisés complexes ou des valeurs RenderFragment, et que les valeurs des paramètres n’ont pas changé,
- Si vous créez un composant d’interface utilisateur uniquement qui ne change pas après le rendu initial, quelle que soit la valeur du paramètre modifiée.
† Pour plus d’informations, consultez la logique de détection des modifications dans Blazorla source de référence (ChangeDetection.cs).
Remarque
Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).
L’exemple d’outil de recherche de vol aérienne suivant utilise des champs privés pour suivre les informations nécessaires pour détecter les modifications. L’identificateur de vol entrant précédent (prevInboundFlightId) et l’identificateur de vol sortant précédent (prevOutboundFlightId) suivent les informations pour la prochaine mise à jour potentielle du composant. Si l’un des identifiants de vol est modifié lors de la définition des paramètres du composant dans OnParametersSet, le composant est réaffiché car shouldRender est défini sur true. Si shouldRender est évalué à false après vérification des identifiants de vol, un réaffichage coûteux peut être évité :
@code {
private int prevInboundFlightId = 0;
private int prevOutboundFlightId = 0;
private bool shouldRender;
[Parameter]
public FlightInfo? InboundFlight { get; set; }
[Parameter]
public FlightInfo? OutboundFlight { get; set; }
protected override void OnParametersSet()
{
shouldRender = InboundFlight?.FlightId != prevInboundFlightId
|| OutboundFlight?.FlightId != prevOutboundFlightId;
prevInboundFlightId = InboundFlight?.FlightId ?? 0;
prevOutboundFlightId = OutboundFlight?.FlightId ?? 0;
}
protected override bool ShouldRender() => shouldRender;
}
Un gestionnaire d’événements peut également affecter shouldRender à true. Dans la majorité des cas, il n’est pas nécessaire de gérer explicitement le rafraîchissement au niveau de chaque gestionnaire d’événement.
Pour plus d’informations, consultez les ressources suivantes :
Virtualisation
Lors du rendu de grandes quantités d’interface utilisateur dans une boucle, par exemple, une liste ou une grille avec des milliers d’entrées, la quantité d’opérations de rendu peut entraîner un décalage dans le rendu de l’interface utilisateur. Étant donné que l’utilisateur ne peut voir qu’un petit nombre d’éléments en même temps sans faire défiler, il est souvent inutile de passer du temps à restituer des éléments qui ne sont pas visibles actuellement.
Blazor fournit le composant Virtualize<TItem> permettant de définir l’apparence et les comportements de défilement d’une liste de taille illimitée, tout en n’affichant que les éléments de la liste situés dans la zone de défilement actuelle. Par exemple, un composant peut afficher une liste avec 100 000 entrées, mais payer uniquement le coût de rendu de 20 éléments visibles.
Pour plus d’informations, consultez Virtualisation des composants ASP.NET Core Razor.
Créer des composants légers et optimisés
La plupart des Razor composants ne nécessitent pas d’efforts d’optimisation agressifs, car la plupart des composants ne se répètent pas dans l’interface utilisateur et ne se rerendent pas à une fréquence élevée. Par exemple, les composants routables avec une @page directive et des composants utilisés pour afficher des éléments de haut niveau de l’interface utilisateur, tels que des boîtes de dialogue ou des formulaires, n’apparaissent probablement qu’un seul à la fois et ne se réaffichent qu'en réponse à un geste de l'utilisateur. Ces composants ne créent généralement pas de charge de travail de rendu élevée. Vous pouvez donc utiliser librement n’importe quelle combinaison de fonctionnalités d’infrastructure sans beaucoup de préoccupations sur les performances de rendu.
Toutefois, il existe des scénarios courants où les composants sont répétés à grande échelle et entraînent souvent des performances médiocres de l’interface utilisateur :
- Formulaires imbriqués volumineux avec des centaines d’éléments individuels, tels que des entrées ou des étiquettes.
- Grilles avec des centaines de lignes ou des milliers de cellules.
- Nuages de points comportant des millions de points de données.
Si vous modélisez chaque élément, cellule ou point de données en tant qu’instance de composant distincte, il existe souvent tant d’entre eux que leurs performances de rendu deviennent critiques. Cette section fournit des conseils sur la légèreté de ces composants afin que l’interface utilisateur reste rapide et réactive.
Évitez de créer des milliers d'instances de composants.
Chaque composant est une île distincte qui peut fonctionner indépendamment de ses parents et de leurs enfants. En choisissant comment fractionner l’interface utilisateur en une hiérarchie de composants, vous contrôlez la granularité du rendu de l’interface utilisateur. Cela peut entraîner des performances bonnes ou médiocres.
En segmentant l’interface utilisateur en composants distincts, il devient possible de ne rafraîchir que des portions réduites lors de la survenue d’événements. Dans un tableau comportant de nombreuses lignes, chacune équipée d’un bouton, il est envisageable de ne rafraîchir que la ligne concernée en recourant à un composant enfant plutôt qu’à la page ou au tableau complet. Toutefois, chaque composant nécessite une surcharge de mémoire et de processeur pour gérer son état indépendant et son cycle de vie de rendu.
Dans un test effectué par les ingénieurs de l’unité de produit core ASP.NET, une surcharge de rendu d’environ 0,06 ms par instance de composant a été observée dans une Blazor WebAssembly application. L’application de test a rendu un composant simple qui accepte trois paramètres. En interne, la surcharge est principalement due à la récupération de l’état par composant à partir de dictionnaires et à la transmission et à la réception de paramètres. Par multiplication, vous pouvez voir que l’ajout de 2 000 instances de composants supplémentaires ajouterait 0,12 secondes au temps de rendu et que l’interface utilisateur commencerait à se sentir lente pour les utilisateurs.
Il est possible de rendre les composants plus légers afin que vous puissiez en avoir plus. Toutefois, une technique plus puissante consiste souvent à éviter d’avoir tant de composants à restituer. Les sections suivantes décrivent deux approches que vous pouvez adopter.
Pour plus d’informations sur la gestion de la mémoire, consultez Gérer la mémoire dans les applications ASP.NET Core déployées côté serveurBlazor.
Intégrer les composants enfants dans leurs composants parents : Prenons l’exemple suivant d’un composant parent qui affiche des composants enfants dans une boucle :
<div class="chat">
@foreach (var message in messages)
{
<ChatMessageDisplay Message="message" />
}
</div>
ChatMessageDisplay.razor :
<div class="chat-message">
<span class="author">@Message.Author</span>
<span class="text">@Message.Text</span>
</div>
@code {
[Parameter]
public ChatMessage? Message { get; set; }
}
L’exemple précédent fonctionne correctement si des milliers de messages ne sont pas affichés en même temps. Pour afficher des milliers de messages à la fois, n’envisagez pas de factoriser le composant distinct ChatMessageDisplay . Intégrez de préférence le composant enfant au sein du composant parent. L’approche suivante évite la surcharge par composant du rendu de tant de composants enfants au coût de perdre la possibilité de réorganiser le balisage de chaque composant enfant indépendamment :
<div class="chat">
@foreach (var message in messages)
{
<div class="chat-message">
<span class="author">@message.Author</span>
<span class="text">@message.Text</span>
</div>
}
</div>
Définissez la notionRenderFragments de « réutilisable » dans le code : il se peut que vous extrayez des composants enfants dans le seul but de réutiliser la logique de rendu. Si c’est le cas, vous pouvez créer une logique de rendu réutilisable sans implémenter de composants supplémentaires. Dans le bloc @code de tout composant, définissez un RenderFragment. Affichez le fragment à partir de n’importe quel emplacement autant de fois que nécessaire :
@RenderWelcomeInfo
<p>Render the welcome content a second time:</p>
@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!</p>;
}
Pour rendre le code RenderTreeBuilder réutilisable à travers plusieurs composants, déclarez le RenderFragmentpublic et static:
public static RenderFragment SayHello = @<h1>Hello!</h1>;
SayHello dans l’exemple précédent peut être appelé à partir d’un composant non lié. Cette technique est utile pour créer des bibliothèques d’extraits de balisage réutilisables qui s’affichent sans surcharge par composant.
RenderFragment les délégués peuvent accepter des paramètres. Le composant suivant transmet le message (message) au RenderFragment délégué :
<div class="chat">
@foreach (var message in messages)
{
@ChatMessageDisplay(message)
}
</div>
@code {
private RenderFragment<ChatMessage> ChatMessageDisplay = message =>
@<div class="chat-message">
<span class="author">@message.Author</span>
<span class="text">@message.Text</span>
</div>;
}
L’approche précédente réutilise la logique de rendu sans surcharge par composant. Toutefois, cette méthode ne permet ni de mettre à jour indépendamment la sous-arborescence de l’interface utilisateur, ni d’éviter son rendu lorsque le composant parent est lui-même rendu, en raison de l’absence de frontière de composant. L’affectation à un délégué RenderFragment n’est prise en charge que dans les fichiers de composants Razor (.razor).
Pour un champ, une méthode ou une propriété non statique qui ne peut pas être référencé par un initialiseur de champ, comme TitleTemplate dans l’exemple suivant, utilisez une propriété au lieu d’un champ pour :RenderFragment
protected RenderFragment DisplayTitle =>
@<div>
@TitleTemplate
</div>;
Ne recevez pas trop de paramètres
Si un composant se répète extrêmement souvent, par exemple, des centaines ou des milliers de fois, la surcharge de transmission et de réception de chaque paramètre s’accumule.
Il est rare que trop de paramètres limitent gravement les performances, mais cela peut être un facteur. Pour un TableCell composant qui affiche 4 000 fois dans une grille, chaque paramètre passé au composant ajoute environ 15 ms au coût total de rendu. La transmission de dix paramètres nécessite environ 150 ms et provoque un décalage de rendu de l’interface utilisateur.
Pour réduire la charge des paramètres, regroupez plusieurs paramètres dans une classe personnalisée. Par exemple, un composant de cellule de table peut accepter un objet commun. Dans l’exemple suivant, Data il est différent pour chaque cellule, mais Options il est courant dans toutes les instances de cellule :
@typeparam TItem
...
@code {
[Parameter]
public TItem? Data { get; set; }
[Parameter]
public GridOptions? Options { get; set; }
}
Toutefois, gardez à l’esprit que le regroupement de paramètres primitifs dans une classe n’est pas toujours un avantage. Bien qu’il puisse réduire le nombre de paramètres, il a également un impact sur le comportement de détection et de rendu des modifications. Le passage de paramètres non primitifs déclenche toujours un nouveau rendu, car Blazor il ne peut pas savoir si les objets arbitraires ont un état mutable en interne, tandis que le passage de paramètres primitifs déclenche uniquement un nouveau rendu si leurs valeurs ont réellement changé.
Considérez également qu’il peut s’agir d’une amélioration de ne pas avoir de composant de cellule de tableau, comme illustré dans l’exemple précédent, et inline plutôt sa logique dans le composant parent.
Remarque
Lorsque plusieurs approches sont disponibles pour améliorer les performances, l’évaluation des approches est généralement nécessaire pour déterminer quelle approche produit les meilleurs résultats.
Pour plus d’informations sur les paramètres de type générique (@typeparam), consultez les ressources suivantes :
- Informations de référence sur la syntaxe Razor pour ASP.NET Core
- Composants ASP.NET Core Razor
- Composants modélisés Blazor ASP.NET Core
S'assurer que les paramètres en cascade sont corrigés
Le CascadingValue composant a un paramètre facultatif IsFixed :
- Si
IsFixedest défini surfalse(par défaut), chaque destinataire de la valeur transmise configure un abonnement pour recevoir des notifications de modification. Chacune d’elles[CascadingParameter]est beaucoup plus coûteuse qu’une normale[Parameter]en raison du suivi de l’abonnement. - S'il
IsFixedesttrue(par exemple,<CascadingValue Value="someValue" IsFixed="true">), les destinataires reçoivent la valeur initiale, mais ne configureront pas d'abonnement pour recevoir des mises à jour. Chacun[CascadingParameter]est léger et pas plus cher qu’un standard[Parameter].
Configurer IsFixed pour true améliore les performances s'il y a de nombreux autres composants qui reçoivent la valeur en cascade. Dans la mesure du possible, définissez IsFixed à true sur des valeurs cascadées. Vous pouvez définir IsFixed sur true quand la valeur fournie ne change pas avec le temps.
Lorsqu'un composant passe this comme valeur en cascade, IsFixed peut également être défini sur true, car this ne change jamais pendant le cycle de vie du composant :
<CascadingValue Value="this" IsFixed="true">
<SomeOtherComponents>
</CascadingValue>
Pour davantage de détails, reportez-vous à la section « Valeurs et paramètres en cascade dans ASP.NET Core Blazor ».
Évitez la propagation d’attributs avec CaptureUnmatchedValues
Les composants peuvent choisir de recevoir des valeurs de paramètre « sans correspondance » à l’aide de l’indicateur CaptureUnmatchedValues :
<div @attributes="OtherAttributes">...</div>
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? OtherAttributes { get; set; }
}
Cette approche permet de transmettre des attributs supplémentaires arbitraires à l’élément. Toutefois, cette approche est coûteuse, car le renderer doit :
- Mettre en correspondance tous les paramètres fournis par rapport à l’ensemble de paramètres connus pour générer un dictionnaire.
- Suivez la façon dont plusieurs copies du même attribut se remplacent mutuellement.
Utilisez CaptureUnmatchedValues là où les performances de rendu des composants ne sont pas critiques, par exemple pour des composants qui ne sont pas souvent répétés. Pour les composants affichés à grande échelle, tels que les éléments d’une longue liste ou les cellules d’une grille, il est préférable de limiter la propagation d’attributs.
Pour plus d’informations, consultez ASP.NET Core Blazor distribution des attributs et paramètres arbitraires.
Implémenter SetParametersAsync manuellement
Une source importante de surcharge de rendu par composant consiste à écrire des valeurs de paramètres entrantes dans les [Parameter] propriétés. Le renderer utilise la réflexion pour écrire les valeurs des paramètres, ce qui peut entraîner des performances médiocres à grande échelle.
Dans certains cas extrêmes, vous pouvez éviter la réflexion et implémenter manuellement votre propre logique de paramètre. Cela peut s’appliquer lorsque :
- Un composant est rendu extrêmement souvent, par exemple, lorsqu’il y a des centaines ou des milliers de copies du composant dans l’interface utilisateur.
- Un composant accepte de nombreux paramètres.
- Vous constatez que la surcharge des paramètres de réception a un impact observable sur la réactivité de l’interface utilisateur.
Dans les cas extrêmes, vous pouvez remplacer la méthode virtuelle SetParametersAsync du composant et implémenter votre propre logique spécifique au composant. L’exemple suivant évite délibérément les recherches de dictionnaire :
@code {
[Parameter]
public int MessageId { get; set; }
[Parameter]
public string? Text { get; set; }
[Parameter]
public EventCallback<string> TextChanged { get; set; }
[Parameter]
public Theme CurrentTheme { get; set; }
public override Task SetParametersAsync(ParameterView parameters)
{
foreach (var parameter in parameters)
{
switch (parameter.Name)
{
case nameof(MessageId):
MessageId = (int)parameter.Value;
break;
case nameof(Text):
Text = (string)parameter.Value;
break;
case nameof(TextChanged):
TextChanged = (EventCallback<string>)parameter.Value;
break;
case nameof(CurrentTheme):
CurrentTheme = (Theme)parameter.Value;
break;
default:
throw new ArgumentException($"Unknown parameter: {parameter.Name}");
}
}
return base.SetParametersAsync(ParameterView.Empty);
}
}
Dans le code précédent, le renvoi de la classe SetParametersAsync de base exécute la méthode de cycle de vie normale sans affecter de paramètres à nouveau.
Comme illustré dans le code précédent, remplacer SetParametersAsync et implémenter une logique personnalisée s’avère complexe et fastidieux ; cette approche est donc généralement déconseillée. Dans les cas extrêmes, l’approche peut générer une petite amélioration du rendu, généralement inférieure à ~10% même à 10 000+ instances de composants lors du ciblage de .NET 10. Les gains potentiels sont plus petits dans les versions ultérieures, car l’attribution de paramètres basée sur la réflexion est optimisée. Elle ne doit être envisagée que dans les cas extrêmes mentionnés précédemment, après réalisation de tests de performance, car les gains obtenus sont souvent compensés par d’autres coûts, notamment le transport des SignalRdifférences (modifications du DOM) pour Interactive Server.
Comme illustré dans le code précédent, remplacer SetParametersAsync et implémenter une logique personnalisée s’avère complexe et fastidieux ; cette approche est donc généralement déconseillée. Dans les cas extrêmes, l’approche peut améliorer les performances de rendu d’ici 20 à 25%, mais vous devez uniquement envisager cette approche dans les scénarios extrêmes répertoriés plus haut dans cette section.
Ne déclenchez pas d’événements trop rapidement
Certains événements du navigateur se produisent avec une fréquence très élevée. Par exemple, onmousemove et onscroll peut déclencher des dizaines ou des centaines de fois par seconde. Dans la plupart des cas, vous n’avez pas besoin d’effectuer les mises à jour de l’interface utilisateur fréquemment. Si des événements sont déclenchés trop rapidement, vous risquez de nuire à la réactivité de l’interface utilisateur ou de consommer un temps processeur excessif.
Au lieu d’utiliser des événements natifs qui se déclenchent rapidement, envisagez d’utiliser l’interopérabilité JS pour enregistrer un rappel qui se déclenche moins fréquemment. Par exemple, le composant suivant affiche la position de la souris, mais ne met à jour qu’une fois toutes les 500 ms :
@implements IDisposable
@inject IJSRuntime JS
<h1>@message</h1>
<div @ref="mouseMoveElement" style="border:1px dashed red;height:200px;">
Move mouse here
</div>
@code {
private ElementReference mouseMoveElement;
private DotNetObjectReference<MyComponent>? selfReference;
private string message = "Move the mouse in the box";
[JSInvokable]
public void HandleMouseMove(int x, int y)
{
message = $"Mouse move at {x}, {y}";
StateHasChanged();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
selfReference = DotNetObjectReference.Create(this);
var minInterval = 500;
await JS.InvokeVoidAsync("onThrottledMouseMove",
mouseMoveElement, selfReference, minInterval);
}
}
public void Dispose() => selfReference?.Dispose();
}
Le code JavaScript associé enregistre un écouteur d’événements DOM pour le mouvement de la souris. Dans cet exemple, l’écouteur d’événements utilise la fonction de throttle Lodash pour limiter le taux d’appels :
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
<script>
function onThrottledMouseMove(elem, component, interval) {
elem.addEventListener('mousemove', _.throttle(e => {
component.invokeMethodAsync('HandleMouseMove', e.offsetX, e.offsetY);
}, interval));
}
</script>
Évitez le réaffichage après le traitement d’événements n’entraînant aucun changement d’état
Les composants héritent de ComponentBase, qui appelle automatiquement StateHasChanged après que les gestionnaires d'événements du composant ont été invoqués. Dans certains cas, il peut être inutile ou indésirable de déclencher un nouveau rendu après l’appel d’un gestionnaire d’événements. Par exemple, un gestionnaire d’événements peut ne pas modifier l’état du composant. Dans ces scénarios, l’application peut tirer parti de l’interface IHandleEvent pour contrôler le comportement de la gestion des Blazorévénements.
Remarque
L’approche de cette section ne génère pas d’exceptions aux limites d’erreur. Pour plus d’informations ainsi qu’un exemple de code prenant en charge les limites d’erreur via l’appel de ComponentBase.DispatchExceptionAsync, consultez AsNonRenderingEventHandler + ErrorBoundary = comportement inattendu (dotnet/aspnetcore #54543).
Pour empêcher les re-rendus pour tous les gestionnaires d’événements d’un composant, implémentez IHandleEvent et fournissez une tâche IHandleEvent.HandleEventAsync qui appelle le gestionnaire d’événements sans appeler StateHasChanged.
Dans l’exemple suivant, aucun gestionnaire d’événements ajouté au composant déclenche un rerender. Par conséquent HandleSelect , aucun gestionnaire d’événements n’entraîne un rerender lorsqu’il est appelé.
HandleSelect1.razor :
@page "/handle-select-1"
@using Microsoft.Extensions.Logging
@implements IHandleEvent
@inject ILogger<HandleSelect1> Logger
<p>
Last render DateTime: @dt
</p>
<button @onclick="HandleSelect">
Select me (Avoids Rerender)
</button>
@code {
private DateTime dt = DateTime.Now;
private void HandleSelect()
{
dt = DateTime.Now;
Logger.LogInformation("This event handler doesn't trigger a rerender.");
}
Task IHandleEvent.HandleEventAsync(
EventCallbackWorkItem callback, object? arg) => callback.InvokeAsync(arg);
}
Outre la prévention globale des réaffichages après l’exécution des gestionnaires d’événements dans un composant, il est possible d’empêcher un réaffichage après un gestionnaire spécifique en utilisant la méthode utilitaire suivante.
Ajoutez la classe suivante EventUtil à une Blazor application. Les actions et fonctions statiques situées en haut de la classe fournissent des gestionnaires qui couvrent plusieurs combinaisons d’arguments et de types de retour qu'utilise EventUtil lorsqu'il gère des événements.
EventUtil.cs :
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public static class EventUtil
{
public static Action AsNonRenderingEventHandler(Action callback)
=> new SyncReceiver(callback).Invoke;
public static Action<TValue> AsNonRenderingEventHandler<TValue>(
Action<TValue> callback)
=> new SyncReceiver<TValue>(callback).Invoke;
public static Func<Task> AsNonRenderingEventHandler(Func<Task> callback)
=> new AsyncReceiver(callback).Invoke;
public static Func<TValue, Task> AsNonRenderingEventHandler<TValue>(
Func<TValue, Task> callback)
=> new AsyncReceiver<TValue>(callback).Invoke;
private record SyncReceiver(Action callback)
: ReceiverBase { public void Invoke() => callback(); }
private record SyncReceiver<T>(Action<T> callback)
: ReceiverBase { public void Invoke(T arg) => callback(arg); }
private record AsyncReceiver(Func<Task> callback)
: ReceiverBase { public Task Invoke() => callback(); }
private record AsyncReceiver<T>(Func<T, Task> callback)
: ReceiverBase { public Task Invoke(T arg) => callback(arg); }
private record ReceiverBase : IHandleEvent
{
public Task HandleEventAsync(EventCallbackWorkItem item, object arg) =>
item.InvokeAsync(arg);
}
}
Appelez EventUtil.AsNonRenderingEventHandler afin d’exécuter un gestionnaire d’événements sans déclencher de rendu lors de son invocation.
Dans l’exemple suivant :
- La sélection du premier bouton, qui invoque
HandleClick1, entraîne un réaffichage. - Sélectionner le deuxième bouton, qui appelle
HandleClick2, ne déclenche pas de re-render. - La sélection du troisième bouton, qui invoque
HandleClick3, ne provoque aucun réaffichage et exploite les arguments d’événement (MouseEventArgs).
HandleSelect2.razor :
@page "/handle-select-2"
@using Microsoft.Extensions.Logging
@inject ILogger<HandleSelect2> Logger
<p>
Last render DateTime: @dt
</p>
<button @onclick="HandleClick1">
Select me (Rerenders)
</button>
<button @onclick="EventUtil.AsNonRenderingEventHandler(HandleClick2)">
Select me (Avoids Rerender)
</button>
<button @onclick="EventUtil.AsNonRenderingEventHandler<MouseEventArgs>(HandleClick3)">
Select me (Avoids Rerender and uses <code>MouseEventArgs</code>)
</button>
@code {
private DateTime dt = DateTime.Now;
private void HandleClick1()
{
dt = DateTime.Now;
Logger.LogInformation("This event handler triggers a rerender.");
}
private void HandleClick2()
{
dt = DateTime.Now;
Logger.LogInformation("This event handler doesn't trigger a rerender.");
}
private void HandleClick3(MouseEventArgs args)
{
dt = DateTime.Now;
Logger.LogInformation(
"This event handler doesn't trigger a rerender. " +
"Mouse coordinates: {ScreenX}:{ScreenY}",
args.ScreenX, args.ScreenY);
}
}
Outre l’implémentation de l’interface IHandleEvent , l’utilisation des autres meilleures pratiques décrites dans cet article peut également aider à réduire les rendus indésirables une fois les événements gérés. Par exemple, le remplacement de ShouldRender dans les composants enfants du composant cible peut servir à contrôler le réaffichage.
Évitez de recréer des délégués pour de nombreux éléments ou composants répétés
La recréation par Blazor de délégués d’expressions lambda pour les éléments ou les composants d’une boucle peut entraîner une baisse des performances.
Le composant suivant présenté dans l’article de gestion des événements affiche un ensemble de boutons. Chaque bouton affecte un délégué à son @onclick événement, ce qui est correct s’il n’y a pas beaucoup de boutons à afficher.
EventHandlerExample5.razor :
@page "/event-handler-example-5"
<h1>@heading</h1>
@for (var i = 1; i < 4; i++)
{
var buttonNumber = i;
<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";
private void UpdateHeading(MouseEventArgs e, int buttonNumber)
{
heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}
}
@page "/event-handler-example-5"
<h1>@heading</h1>
@for (var i = 1; i < 4; i++)
{
var buttonNumber = i;
<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";
private void UpdateHeading(MouseEventArgs e, int buttonNumber)
{
heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}
}
Si un grand nombre de boutons sont rendus à l’aide de l’approche précédente, la vitesse de rendu est défavorablement affectée, ce qui entraîne une mauvaise expérience utilisateur. Pour afficher un grand nombre de boutons avec un rappel d’événement de clic, l’exemple suivant utilise une collection d’objets bouton qui réalisent l’affectation du délégué @onclick de chaque bouton à un Action. L'approche suivante ne nécessite pas Blazor de reconstruire tous les délégués de bouton chaque fois que les boutons sont affichés :
LambdaEventPerformance.razor :
@page "/lambda-event-performance"
<h1>@heading</h1>
@foreach (var button in Buttons)
{
<p>
<button @key="button.Id" @onclick="button.Action">
Button #@button.Id
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";
private List<Button> Buttons { get; set; } = new();
protected override void OnInitialized()
{
for (var i = 0; i < 100; i++)
{
var button = new Button();
button.Id = Guid.NewGuid().ToString();
button.Action = (e) =>
{
UpdateHeading(button, e);
};
Buttons.Add(button);
}
}
private void UpdateHeading(Button button, MouseEventArgs e)
{
heading = $"Selected #{button.Id} at {e.ClientX}:{e.ClientY}";
}
private class Button
{
public string? Id { get; set; }
public Action<MouseEventArgs> Action { get; set; } = e => { };
}
}