Note
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier les répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de changer de répertoire.
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 9 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.
Éviter le rendu inutile 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. Vous ne devez vous préoccuper que d'ignorer les sous-arborescences qui sont particulièrement coûteuses à rendre et qui provoquent un ralentissement de l'interface utilisateur.
Au moment de l’exécution, les composants existent dans une hiérarchie. Un composant racine (le premier composant chargé) a des composants enfants. À son tour, les enfants de la racine ont 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 distribué au composant qui a exécuté le gestionnaire de l'événement. Après avoir exécuté le gestionnaire d’événements, le composant est rerendu.
- 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 se rechargent si
ShouldRenderrenvoietrue, ce qui est le comportement par défaut sauf si remplacé, et les valeurs de paramètre peuvent avoir changé, par exemple, s'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 ciblant des composants avancés peuvent entraîner un rerendu coûteux, car chaque composant en dessous du composant avancé doit être rerendu.
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. La logique intégrée de détection des modifications ignore automatiquement le re-rendu si les valeurs de paramètre immuables n’ont pas changé. 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 identificateurs de vol change lorsque les paramètres du composant sont définis dans OnParametersSet, le composant est re-rendu, car shouldRender il est défini sur true. Si shouldRender est évalué à false après vérification des identificateurs de vol, un nouveau rendu coûteux est é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. Pour la plupart des composants, il n’est généralement pas nécessaire de déterminer le réamenage au niveau des gestionnaires d’événements individuels.
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 Virtualize<TItem> composant pour créer l’apparence et les comportements de défilement d’une liste de taille arbitraire tout en rendant uniquement les éléments de liste qui se trouvent dans la fenêtre de visualisation 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 avec 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 fractionnant l'interface utilisateur en composants distincts, vous pouvez faire en sorte que des parties plus petites de l'interface utilisateur se rechargent lorsque des événements se produisent. Dans une table avec de nombreuses lignes qui ont un bouton dans chaque ligne, vous ne pouvez avoir que cette seule ligne remanie à l’aide d’un composant enfant au lieu de l’ensemble de la page ou de la table. 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égration des composants enfants dans leurs parents : Considérez la partie suivante d’un composant parent qui rend les 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 le composant enfant dans le 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 des composants réutilisables RenderFragments dans le code : vous pouvez factoriser les composants enfants uniquement pour 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 d’un @code 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, l’approche n’autorise pas l’actualisation de la sous-arborescence de l’interface utilisateur indépendamment, et elle n’a pas la possibilité d’ignorer le rendu de la sous-arborescence de l’interface utilisateur lorsque son parent s’affiche, car il n’y a pas de limite de composant. L’affectation à un RenderFragment délégué n’est prise en charge que dans les Razor fichiers de composant (.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 basés sur un modèle 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 plus d’informations, consultez ASP.NET Core valeurs et paramètres en cascadeBlazor.
Éviter la répartition des 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 qui s’affichent à grande échelle, tels que chaque élément d’une liste volumineuse ou dans les cellules d’une grille, essayez d’éviter la répartition d’attributs.
Pour plus d’informations, consultez ASP.NET Core Blazor paramètres de platissement d’attributs et 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 vous pouvez le voir dans le code précédent, surcharger et fournir une logique personnalisée sont complexes et laborieux, nous vous déconseillons donc généralement d’adopter cette approche. Dans les cas extrêmes, il peut améliorer les performances de rendu de 20 à 25%, mais vous devez uniquement envisager cette approche dans les scénarios extrêmes énumérés précédemment dans cette section.
Ne déclenchez pas d’événements trop rapidement
Certains événements de navigateur se déclenchent extrêmement fréquemment. 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 correspondant enregistre l’écouteur d’événements DOM pour le déplacement 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 de réactiver après la gestion des événements sans modifications 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 et le code de démonstration qui prend en charge les limites d’erreur en appelant 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);
}
En plus d’empêcher les rerenders après que les gestionnaires d’événements se déclenchent dans un composant de manière globale, il est possible d’empêcher les rerenders après un seul gestionnaire d’événements 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 un gestionnaire d’événements qui ne déclenche pas de rendu lorsqu’il est appelé.
Dans l’exemple suivant :
- La sélection du premier bouton, qui appelle
HandleClick1, déclenche une nouvelle présentation. - Sélectionner le deuxième bouton, qui appelle
HandleClick2, ne déclenche pas de re-render. - La sélection du troisième bouton, qui appelle
HandleClick3, ne déclenche pas de nouvelleender et utilise des 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, surcharger ShouldRender dans les composants enfants du composant cible peut être utilisé pour contrôler le rendu.
Évitez de recréer des délégués pour de nombreux éléments ou composants répétés
Blazorla récréation des délégués d’expression lambda pour les éléments ou les composants d’une boucle peut entraîner des performances médiocres.
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 pour les événements de clic, l’exemple suivant utilise une collection d’objets de bouton qui attribuent le délégué de @onclick 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 => { };
}
}