Blazor : EventCallback
La communication enfant vers parent : EventCallback sans valeur, EventCallback<T> avec une valeur (int, objet, record pour plusieurs champs), et pourquoi EventCallback plutôt qu'Action ou Func.
Blazor : EventCallback
En Blazor, un enfant ne remonte jamais l'information en modifiant son parent directement : il le prévient, et le parent décide quoi faire. Ce canal, c'est l'EventCallback, effleuré dans les composants. On le déplie ici.
# Un signal sans valeur
Sans donnée à transporter, EventCallback est un simple déclencheur. Le parent passe un handler, l'enfant l'invoque :
@* Bouton.razor *@
<button @onclick="() => OnClic.InvokeAsync()">@Libelle</button>
@code {
[Parameter] public string Libelle { get; set; } = "";
[Parameter] public EventCallback OnClic { get; set; }
}@* Bouton.razor *@<button @onclick="() => OnClic.InvokeAsync()">@Libelle</button> @code { [Parameter] public string Libelle { get; set; } = ""; [Parameter] public EventCallback OnClic { get; set; }}@* Parent *@
<Bouton Libelle="Valider" OnClic="Valider" />
@code {
private void Valider() { /* logique du parent */ }
}@* Parent *@<Bouton Libelle="Valider" OnClic="Valider" /> @code { private void Valider() { /* logique du parent */ }} EventCallback est un struct, jamais null. Tu peux appeler InvokeAsync même si le parent n'a fourni aucun handler : rien ne se passe, sans exception. Pas besoin du if (OnClic != null) qu'imposerait un Action.
Si tu veux transmettre l'événement DOM brut, type le paramètre en EventCallback<MouseEventArgs> : tu peux alors câbler @onclick="OnClic" directement, sans lambda.
# Remonter une valeur typée
Le vrai intérêt : EventCallback<T> transporte une valeur de l'enfant vers le parent.
@* Notation.razor : l'utilisateur clique une étoile, on remonte la note *@
@for (int i = 1; i <= 5; i++)
{
var valeur = i; // capture : sinon la closure verrait i = 6 après la boucle
<button class="etoile @(valeur <= Note ? "pleine" : "")"
@onclick="() => NoteChoisie.InvokeAsync(valeur)">★</button>
}
@code {
[Parameter] public int Note { get; set; }
[Parameter] public EventCallback<int> NoteChoisie { get; set; }
}@* Notation.razor : l'utilisateur clique une étoile, on remonte la note *@@for (int i = 1; i <= 5; i++){ var valeur = i; // capture : sinon la closure verrait i = 6 après la boucle <button class="etoile @(valeur <= Note ? "pleine" : "")" @onclick="() => NoteChoisie.InvokeAsync(valeur)">★</button>} @code { [Parameter] public int Note { get; set; } [Parameter] public EventCallback<int> NoteChoisie { get; set; }}Côté parent, le handler reçoit la valeur :
<Notation Note="note" NoteChoisie="EnregistrerNote" />
<p>Ta note : @note / 5</p>
@code {
private int note;
private void EnregistrerNote(int valeur) => note = valeur;
}<Notation Note="note" NoteChoisie="EnregistrerNote" /><p>Ta note : @note / 5</p> @code { private int note; private void EnregistrerNote(int valeur) => note = valeur;}# Remonter un objet
Le T peut être n'importe quel type : ici, l'enfant remonte l'article cliqué.
@* ListeArticles.razor *@
@foreach (var article in Articles)
{
<li @onclick="() => ArticleSelectionne.InvokeAsync(article)">@article.Nom</li>
}
@code {
[Parameter] public IReadOnlyList<Article> Articles { get; set; } = [];
[Parameter] public EventCallback<Article> ArticleSelectionne { get; set; }
}@* ListeArticles.razor *@@foreach (var article in Articles){ <li @onclick="() => ArticleSelectionne.InvokeAsync(article)">@article.Nom</li>} @code { [Parameter] public IReadOnlyList<Article> Articles { get; set; } = []; [Parameter] public EventCallback<Article> ArticleSelectionne { get; set; }}# Remonter plusieurs valeurs
Un EventCallback<T> ne transporte qu'un seul T. Pour en remonter plusieurs, emballe-les dans un record (ou un tuple) :
public record TriDemande(string Colonne, bool Ascendant);public record TriDemande(string Colonne, bool Ascendant);@code {
[Parameter] public EventCallback<TriDemande> TriChange { get; set; }
// On emballe les deux valeurs dans le record, puis on l'envoie
private Task TrierParPrix() => TriChange.InvokeAsync(new("prix", true));
}
<button @onclick="TrierParPrix">Trier par prix</button>@code { [Parameter] public EventCallback<TriDemande> TriChange { get; set; } // On emballe les deux valeurs dans le record, puis on l'envoie private Task TrierParPrix() => TriChange.InvokeAsync(new("prix", true));} <button @onclick="TrierParPrix">Trier par prix</button># EventCallback vs Action / Func
On pourrait typer le paramètre en Action ou Func<Task>. Deux raisons de préférer EventCallback :
- Re-render automatique : après l'appel, Blazor déclenche le rendu du parent tout seul. Avec un
Action, le parent ne se rafraîchit pas et tu dois appelerStateHasChanged()à la main. - Awaitable :
InvokeAsyncaccepte aussi bien un handler synchrone qu'async, et l'attend proprement. UnActionne peut pas porter d'await.
@* Avec Action : le parent ne se re-rend PAS tout seul *@
<Enfant OnChose="() => { compteur++; StateHasChanged(); }" />
@* Avec EventCallback : le re-render est automatique *@
<Enfant OnChose="() => compteur++" />@* Avec Action : le parent ne se re-rend PAS tout seul *@<Enfant OnChose="() => { compteur++; StateHasChanged(); }" /> @* Avec EventCallback : le re-render est automatique *@<Enfant OnChose="() => compteur++" />C'est le mécanisme idiomatique de la communication enfant vers parent en Blazor.
# À retenir
- Un enfant remonte l'info au parent via un
EventCallback, jamais en le modifiant directement. EventCallback<T>transporte une valeur : unint, un objet, ou unrecordpour plusieurs champs.- C'est un struct :
InvokeAsyncest sûr même sans handler côté parent (pas de null-check). - Préfère-le à
Action/Func: re-render automatique du parent + awaitable. - Piège récurrent : dans une boucle
for, capture la variable (var valeur = i;) avant de l'utiliser dans la closure.