Module 10 – Performance et optimisation
Niveau 5.1 – Laravel Livewire
wire:key, debounce, defer, #[Locked], wire:init : leviers pour garder les composants rapides. En fin de module : profilage, lazy loading, bonnes pratiques en production.
Objectif
À la fin de ce module, vous saurez :
- Réduire le nombre et la taille des requêtes Livewire : wire:model.defer ou debounce sur les champs, pagination pour les listes longues, et évitement des propriétés inutilement lourdes (gros objets, collections entières).
- Utiliser wire:key sur les listes dynamiques (boucles) pour que Livewire fasse correctement le morphing du DOM et évite les bugs d’affichage ou de focus.
- Comprendre wire:init (chargement différé) et #[Locked] pour limiter ce qui est sérialisé et modifiable par le client.
- Appliquer les bonnes pratiques : requêtes Eloquent optimisées (with(), select()), pas de N+1, et feedback utilisateur (loading) pour que l’app paraisse réactive.
Pourquoi optimiser ?
Chaque interaction (wire:click, mise à jour wire:model) envoie une requête AJAX avec l’état du composant (toutes les propriétés publiques sérialisées) et reçoit le nouveau HTML (ou un diff). Si l’état est lourd (grosses collections, gros objets) ou si les requêtes partent à chaque frappe sans debounce, l’application peut sembler lente et la charge serveur augmenter. En optimisant la taille du payload, la fréquence des requêtes et le coût du rendu (requêtes BDD, N+1), on améliore la fluidité et la scalabilité.
wire:key pour les listes dynamiques
Quand vous bouclez sur une liste (ex. @foreach ($items as $item)), le DOM contient plusieurs éléments similaires. Lors d’un re-render, Livewire compare l’ancien et le nouveau HTML pour mettre à jour uniquement ce qui a changé (morphing). Pour qu’il puisse associer correctement les éléments (savoir quel bloc correspond à quel élément de la liste), il faut une clé unique par élément : wire:key="...".
Sans wire:key, si l’ordre ou le nombre d’éléments change (tri, filtre, suppression), Livewire peut réutiliser les mauvais nœuds DOM (ex. un input garde la valeur d’un autre élément). Avec wire:key="item-{{ $item->id }}", chaque ligne est identifiée de façon stable.
Exemple :
@foreach ($items as $item)
<div wire:key="item-{{ $item->id }}">
<input wire:model="items.{{ $item->id }}.name">
{{ $item->name }}
</div>
@endforeach
Utilisez une clé unique et stable (ID en BDD, ou index si la liste n’est pas réordonnable). Évitez une clé qui change à chaque rendu (ex. wire:key="item-{{ rand() }}").
Réduire la fréquence des requêtes
- wire:model.live.debounce.300ms sur les champs de recherche : une requête part 300 ms après la dernière frappe au lieu d’une à chaque caractère.
- wire:model.defer sur les champs de formulaire : les valeurs ne sont envoyées qu’au submit ou au clic sur un bouton d’action. Moins de requêtes, comportement prévisible.
- wire:model.lazy : mise à jour au blur. Utile pour des champs qui n’ont pas besoin d’un feedback immédiat à chaque frappe.
Cela réduit la charge serveur et améliore la sensation de réactivité (moins de « lag » à chaque touche).
Réduire le poids du payload : propriétés et sérialisation
L’état du composant (toutes les propriétés publiques) est sérialisé à chaque requête (envoyé au serveur et renvoyé au client). Pour des composants performants :
- Ne pas stocker de grosses collections ou de gros objets si vous n’en avez pas besoin entre les requêtes. Par exemple, une liste paginée : garder $search, $sortField, $page, etc., et calculer la liste dans render() à partir du modèle Eloquent ; ne pas mettre la collection entière dans une propriété publique.
- #[Locked] (Livewire 3) : une propriété locked n’est pas mise à jour depuis le payload entrant. Elle est quand même sérialisée dans la réponse (pour affichage), mais le client ne peut pas la modifier. Utile pour des IDs ou des valeurs fixées au chargement.
- Éviter de passer des modèles Eloquent avec des relations chargées inutilement. Livewire sérialise les modèles (par ID et attributs) ; si vous devez les garder en propriété publique, chargez uniquement les relations nécessaires et limitez les attributs si possible.
wire:init (chargement différé)
wire:init permet d’exécuter une action (appel de méthode) après le premier rendu du composant. Par exemple, charger des données coûteuses seulement quand le composant est visible, au lieu de les charger dans mount() (qui bloque le premier rendu). Vous pouvez appeler une méthode qui remplit une propriété (ex. $stats) et la vue se met à jour dès que la méthode a fini.
Exemple (conceptuel) :
<div wire:init="loadStats">
@if ($stats)
<!-- afficher les stats -->
@else
Chargement des statistiques...
@endif
</div>
Dans la classe : public function loadStats(): void { $this->stats = ...; }. Au premier affichage, loadStats est appelée ; quand elle termine, le composant se re-rend avec $stats rempli. Utile pour des blocs secondaires (widgets, tableaux de bord) pour ne pas bloquer le rendu principal.
Requêtes Eloquent et N+1
Déjà vu au module 6 : with() pour charger les relations en une requête, select() pour limiter les colonnes, paginate() pour ne pas charger des milliers de lignes. Ces bonnes pratiques réduisent le temps de render() et la charge BDD. En Livewire, render() est appelé à chaque action ; des requêtes lourdes peuvent ralentir toutes les interactions du composant.
À retenir
- wire:key sur les éléments de listes dynamiques (boucles) pour un morphing correct et éviter les bugs de focus/valeurs.
- Debounce sur la recherche, defer ou lazy sur les champs pour réduire le nombre de requêtes.
- Pagination pour les listes longues ; ne pas mettre de grosses collections dans des propriétés publiques.
- #[Locked] pour les propriétés non modifiables par le client ; garder l’état minimal (IDs, filtres, page) et recalculer le reste dans render().
- wire:init pour charger des données après le premier rendu si besoin. with() et select() pour des requêtes Eloquent optimisées.
Approfondissement
- Profiler : en local, surveillez la taille des réponses Livewire (onglet Network, payload et réponse). Si le HTML renvoyé ou l’état sérialisé est énorme, réduisez les propriétés publiques ou le contenu rendu (pagination, select()).
- Lazy loading de composants : pour une page avec plusieurs blocs Livewire (ex. dashboard avec 5 widgets), chaque composant est rendu et son état sérialisé. Si un bloc est coûteux et secondaire, wire:init ou le charger dans un second temps (ex. composant qui ne fait render() qu’au premier wire:init) peut alléger le premier rendu.
- Production : activer le cache Laravel (config, routes, vues) ; OPcache pour PHP. Côté Livewire, éviter de stocker des collections entières en propriétés publiques ; privilégier render() qui recalcule à partir de filtres/page.
Dans le prochain module, nous voyons les tests des composants Livewire avec Livewire::test(), call(), set(), et les assertions assertSet, assertSee, assertRedirect.