Fermer

avril 3, 2024

Performances améliorées grâce à la virtualisation des composants

Performances améliorées grâce à la virtualisation des composants


Nous apprendrons comment la virtualisation des composants optimise les performances de rendu des applications Blazor.

Dans cet article du Les bases du Blazor série, nous apprendrons comment utiliser la virtualisation des composants dans Blazor pour optimiser les performances de rendu des applications Web Blazor.

Tu peux accéder au code utilisé dans cet exemple sur GitHub.

Introduction

Lorsque nous travaillons avec des applications basées sur les données, nous devons tôt ou tard afficher un nombre inconfortable d’éléments dans une liste. En tant que développeurs, nous souhaitons toujours garder l’interface utilisateur aussi nette que possible et inclure uniquement les données nécessaires pour effectuer une action particulière.

Mais que se passe-t-il si le propriétaire du produit demande le rendu de 10 000 ou 100 000 éléments ? Et si le raisonnement était que « le client en a besoin » ? En bon développeur, nous proposons une solution qui fonctionne.

Le problème fondamental lié au rendu de milliers ou de centaines de milliers d’éléments est que le rendu prend du temps, utilise beaucoup de mémoire et rend généralement le site Web lent.

Virtualisation des composants est une fonctionnalité Blazor qui nous aide à trouver une solution à de tels scénarios.

Le problème sans virtualisation

Avant de pouvoir implémenter la virtualisation, nous devons d’abord créer un exemple de ce à quoi pourrait ressembler le rendu de milliers d’éléments dans Blazor.

Considérez le code suivant qui affiche les commandes à l’écran.

<h1>Orders</h1>
@foreach (var order in Orders)
{
    <div style="display:flex;">
        <div style="width: 400px;">@order.Id</div>
        <div>$ @order.Value</div>
    </div>
}

@code {
    public record Order(Guid Id, int Value);

    public IList<Order> Orders { get; set; } = new List<Order>();

    protected override void OnInitialized()
    {
        var random = new Random();
        for (int i = 0; i < 100_000; i++)
        {
            Orders.Add(new Order(Guid.NewGuid(), random.Next(20, 9999)));
        }
    }
}

Nous définissons une liste de Order objets contenant un Id et un Value propriété. Dans le OnInitialized méthode du cycle de vie, nous créons cent mille commandes et les ajoutons au Orders liste.

Dans le modèle de composant, nous avons un foreach instruction pour afficher tous les éléments à l’écran. Dans cet exemple, nous ne rendons que trois divs pour chaque commande. Dans un contexte plus complexe, il pourrait s’agir d’une arborescence d’objets complète, comprenant de nombreux autres éléments HTML.

Même si nous utilisons un balisage HTML relativement simple, nous avons déjà un retard notable lors de la navigation vers la page. Aussi, avec 1,4 Go de mémoire utilisé, ce n’est certainement pas idéal.

Comment mettre en œuvre la virtualisation

Blazor fournit un Virtualize composant qui simplifie la virtualisation des composants. Et le meilleur : nous pouvons virtualiser rien. Peu importe qu’il s’agisse d’un autre composant Blazor, d’un simple élément HTML ou d’un mélange des deux.

Le code du modèle suivant montre comment virtualiser l’exemple présenté ci-dessus.

<Virtualize Items="Orders" Context="order">
    <div style="display:flex;">
        <div style="width: 400px;">@order.Id</div>
        <div>$ @order.Value</div>
    </div>
</Virtualize>

Le Virtualize Le composant expose quelques propriétés. Les plus importants sont Items et Context. Nous fournissons la liste des éléments que nous souhaitons virtualiser comme argument du Items propriété. Et nous fournissons un nom pour chaque élément comme valeur du Context propriété.

Nous pouvons alors utiliser la variable order dans le Virtualize composant pour définir le modèle pour chaque élément.

Lors de l’exécution de l’application Blazor à l’aide de la virtualisation des composants, je n’ai pas remarqué de retard lors du rendu de la page. Également consommation de mémoire dans Google Chrome est descendu à 100 Mo à partir de 1,4 Go avant sans virtualisation.

Nous obtenons un chargement de page rapide et moins de consommation de mémoire avec une chose aussi simple que d’encapsuler notre code d’article dans un composant Virtualize.

Comment ça marche?

Vous vous demandez peut-être comment cela fonctionne en coulisses. Il est tentant de supposer que Virtualize Le composant implémente la pagination, ce qui signifie qu’il charge uniquement les éléments visibles à l’écran. Malheureusement, ce n’est pas si simple.

Lorsque nous initialisons la liste des éléments dans le OnInitialized méthode, nous avons déjà récupéré tous les éléments en mémoire. Envelopper notre code de modèle dans un Virtualize le composant ne change rien à cela. Les éléments sont toujours en mémoire.

Cependant, seul un nombre limité d’éléments sont affichés à l’écran. Nous pouvons le constater en ouvrant les outils du développeur et en regardant l’onglet éléments. Avec la virtualisation, nous ne voyons qu’un nombre limité de divs.

Un site Web avec les articles commandés à gauche.  À droite, les outils de développement Chrome avec les éléments HTML rendus.

En revanche, sans virtualisation, la page restitue un div pour chaque élément de la liste.

Contrôler le comportement de rendu

Lors du défilement, le Virtualize Le composant restitue des composants supplémentaires, ce qui le rend transparent pour l’utilisateur.

Le Virtualize Le composant a une implémentation interne qui décide du nombre d’éléments qui sont rendus au-delà de ce qui est actuellement visible à l’écran. L’algorithme interne est basé sur la hauteur d’un élément et la hauteur de son conteneur.

Nous pouvons utiliser le Virtualize Composants OverscanCount propriété pour modifier le nombre d’éléments supplémentaires rendus.

<Virtualize Items="Orders" Context="order" OverscanCount="15">
    <div style="display:flex;">
        <div style="width: 400px;">@order.Id</div>
        <div>$ @order.Value</div>
    </div>
</Virtualize>

Je garde généralement la valeur par défaut lorsque cela est possible. Parfois, en fonction de la complexité et de la taille du contenu d’un seul élément, vous souhaitez définir manuellement le OverscanCount propriété pour mieux contrôler son comportement de rendu.

Pour mon exemple ci-dessus, l’implémentation par défaut restitue environ 15 éléments supplémentaires.

Chargement paresseux d’éléments

Mais que se passe-t-il si le chargement des articles coûte cher ? Et si nous voulons charger uniquement les éléments réellement visibles à l’écran ? Oui c’est possible.

Le Lazy Loading utilise le ItemsProvider propriété au lieu de Items propriété du Virtualize composant.

<Virtualize ItemsProvider="@LoadOrders" Context="order">
    <div style="display:flex;">
        <div style="width: 400px;">@order.Id</div>
        <div>$ @order.Value</div>
    </div>
</Virtualize>

Important: Nous pouvons utiliser soit le Items propriété avec une collection qui contient tous les éléments ou les ItemsProvider propriété avec une méthode qui charge les éléments, mais nous ne pouvons pas les définir tous les deux. Sinon, nous aurons un InvalidOperationException lors de l’exécution.

Jetons maintenant un coup d’œil à LoadOrders méthode placée dans le @code section du composant de page LazyLoading.

private ValueTask<ItemsProviderResult<Order>> LoadOrders(ItemsProviderRequest request)
{
    StartIndex = request.StartIndex;
    Count = request.Count;

    StateHasChanged();

    var filteredOrders = Orders.Skip(request.StartIndex)
        .Take(request.Count);

    var result = new ItemsProviderResult<Order>(filteredOrders, Orders.Count());
    return ValueTask.FromResult(result);
}

Tout d’abord, nous avons ajouté deux propriétés int au composant page pour une meilleure illustration du chargement paresseux.

public int StartIndex { get; set; }
public int Count { get; set; }

Le LoadOrders la méthode obtient un seul argument de type ItemsProviderRequest. Il contient un StartIndex et un Count propriété utilisée pour définir quelle partie de l’ensemble de données est demandée.

Nous prenons ces deux propriétés, définissons leurs valeurs sur leurs propriétés respectives dans le composant page et appelons le StateHasChanged méthode pour faire savoir au composant de page qu’il doit être restitué. Il est nécessaire de voir l’état correct.

Nous affichons le décompte et l’index de départ dans le cadre du titre de la page. C’est le moyen le plus simple de garder les informations visibles pour l’utilisateur.

<PageTitle>Lazy Loading Orders - @StartIndex:@Count</PageTitle>

Ces trois premières lignes du LoadOrders la méthode est essentiellement destinée à la démonstration et à l’apprentissage de ItemsProviders travailler sous le capot. Dans une implémentation réelle, nous commençons par le code suivant.

Nous utilisons LINQ pour filtrer les données du Orders propriété pour contenir uniquement les données demandées par le ItemsProviderRequest.

Ensuite, nous créons une instance du générique ItemsProviderResult tapez et fournissez les commandes filtrées et leur nombre.

La signature de la méthode nous oblige à renvoyer un ValueTask d’un ItemsProviderResult. Nous utilisons le ValueTask.FromResult méthode dans ce cas car notre code n’est pas asynchrone.

Note: Encore une fois, dans cet exemple, nous chargeons tous les éléments lors de la création du composant de page et les stockons dans la propriété Orders. Dans un scénario dans lequel vous souhaitez accéder à une API et charger les commandes à partir de la base de données, vous ne chargeriez pas toutes les commandes à l’avance. Au lieu de cela, vous pouvez utiliser le LoadOrders méthode pour écrire du code qui appelle l’API et fournir le StartIndex et le Count propriétés pour définir quel sous-ensemble de données doit être chargé.

Lors de l’exécution de l’application, vous pouvez voir le 0:49 ajouté à la vignette de la page (affichée dans l’onglet du navigateur). Cela signifie que nous commençons à l’index 0 et chargeons 49 éléments.

Un site Web avec des articles commandés avec un identifiant et un montant.  Dans le titre de la page, le startIndex et le count sont affichés pour démontrer l'effet de chargement paresseux.  Affichage de 0:49.

Lors du défilement du contenu, ces chiffres se mettent à jour et nous voyons que le ItemsProvider La méthode est appelée chaque fois que nous dépassons les éléments actuellement chargés.

Un site Web avec des articles commandés avec un identifiant et un montant.  Dans le titre de la page, le startIndex et le count sont affichés pour démontrer l'effet de chargement paresseux.  Affichage 90:49.

N’oubliez pas que vous pouvez accéder au code utilisé dans cet exemple sur GitHub. En particulier, l’exemple du chargement différé pourrait être plus simple à comprendre lorsque l’on travaille directement dans le code.

Des pièges avec la virtualisation et le chargement paresseux

Le fourni Virtualize Le composant effectue beaucoup de travail pour réduire les performances de rendu requises pour afficher des listes massives de données.

Il existe cependant certaines limites. Par exemple, on s’attend à ce que tous les éléments aient la même hauteur. Sinon, le Virtualize Le composant n’est pas capable de calculer la plage de défilement et, par conséquent, ne sait pas quoi restituer.

De plus, si vous appelez une API coûteuse tous les 40 à 50 éléments, cela peut ajouter beaucoup de frais généraux. Par exemple, vous pouvez avoir des requêtes HTTP, y compris la latence pour chaque requête. Parfois, il peut être préférable de charger toutes les données à l’avance, même si l’utilisateur doit attendre que l’ensemble des données soit téléchargé.

Comme toujours en génie logiciel, cette fonctionnalité n’est pas utilisable à chaque fois. Il s’agit plutôt d’un élément supplémentaire dans la boîte à outils. Utilisez-le lorsque cela a du sens et utilisez autre chose lorsque cela ne vous convient pas.

Conclusion

L’intégré Virtualize Le composant nous permet de restituer efficacement de grands ensembles de données en rendre uniquement les éléments visibles à l’écran. Le composant utilise des algorithmes internes pour décider du nombre d’éléments à afficher à la fois.

Nous pouvons utiliser le Items propriété pour fournir une liste préremplie de données ou un ItemProvider pour charger les données paresseusement au fur et à mesure. Les deux approches ont leurs avantages et leurs inconvénients, comme expliqué dans cet article.

Le OverscanCount nous permet de contrôler le nombre d’éléments rendus simultanément. Il peut être utile de définir cette propriété pour ajuster le comportement à votre cas d’utilisation. Augmentez-le lorsqu’un appel d’API utilisant chargement paresseux prend beaucoup de temps. Diminuez-le lors du rendu de composants simples en mémoire pour améliorer les performances de chargement des pages.

Dans l’ensemble, le système intégré Virtualize Le composant peut vous aider à écrire des applications Blazor plus efficaces.

Tu peux accéder au code utilisé dans cet exemple sur GitHub.

Si vous souhaitez en savoir plus sur le développement de Blazor, vous pouvez regarder mon cours intensif Blazor gratuit sur YouTube. Et restez à l’écoute du blog Telerik pour en savoir plus Les bases du Blazor.




Source link