Hur Blazor håller UI:t synkat med C#-tillståndet, hur två-vägs-binding fungerar, och hur komponenter kommunicerar med EventCallback.
@bind, klickhändelser, EventCallback och vad StateHasChanged egentligen gör.
En-vägs binding är "läs från C# till UI" — vilket @variabel redan ger oss. Två-vägs binding går åt båda hållen och skrivs med @bind.
@page "/binding-demo"
<h1>Hej, @namn!</h1>
<input @bind="namn" /> @* uppdaterar vid blur *@
<input @bind="namn" @bind:event="oninput" /> @* uppdaterar vid varje tangent *@
@code {
private string namn = "Världen";
}
@bind egentligen gör
Det är syntactic sugar för två saker: en value="@namn"-attribut och en @onchange-handler som sätter namn till det nya värdet. Du kan göra det manuellt om du behöver mer kontroll.
<input type="date" @bind="birthDate" @bind:format="yyyy-MM-dd" />
@code {
private DateTime birthDate = DateTime.Today;
}
<button @onclick="Increment">+1</button>
<button @onclick="@(() => count -= 1)">-1</button>
<button @onclick="Reset">Återställ</button>
<p>Antal: @count</p>
@code {
private int count = 0;
private void Increment() => count++;
private void Reset() => count = 0;
}
| Direktiv | Användning |
|---|---|
@onclick | Klick |
@oninput | Vid varje tangenttryckning i input |
@onchange | Vid värdeändring — triggas när elementet tappar fokus (blur) med ett nytt värde |
@onsubmit | Formulärinlämning |
@onkeydown / @onkeyup | Tangenttryckning |
@onmouseover / @onmouseout | Musrörelser |
Ett HTML-element har fokus när det är aktivt — d.v.s. när du klickat i det eller tabbar till det med tangentbordet. Ett textfält med fokus tar emot tangenttryckningar.
Blur är motsatsen: elementet tappar fokus — till exempel när du klickar någon annanstans på sidan eller tabbar vidare till nästa fält.
Det är vid blur som @onchange triggas för textinput — alltså inte för varje bokstav du skriver, utan en gång när du "lämnar" fältet. Det innebär:
@onchange → ingenting händer i C# medan du skriver.@onchange triggas och C#-variabeln uppdateras till "Hej".@oninput i stället, eller kombinera @bind med @bind:event="oninput".@* @onchange — uppdateras vid blur (fokustapp) *@
<input @onchange="e => namn = e.Value?.ToString() ?? """ value="@namn" />
@* @oninput — uppdateras direkt vid varje tangenttryckning *@
<input @oninput="e => namn = e.Value?.ToString() ?? """ value="@namn" />
@* @bind är default onchange; @bind:event="oninput" ändrar till oninput *@
<input @bind="namn" />
<input @bind="namn" @bind:event="oninput" />
<p>Namn: @namn</p>
@code { private string namn = ""; }
<input @onkeydown="HandleKey" />
<div @onclick="HandleClick">Klicka var som helst</div>
@code {
private void HandleKey(KeyboardEventArgs e)
{
if (e.Key == "Enter") Console.WriteLine("Enter trycktes");
}
private void HandleClick(MouseEventArgs e)
{
Console.WriteLine($"Klick på ({e.ClientX}, {e.ClientY})");
}
}
<button @onclick="LoadData" disabled="@isLoading">
@(isLoading ? "Laddar..." : "Hämta data")
</button>
@code {
private bool isLoading;
private List<Product> products = new();
private async Task LoadData()
{
isLoading = true;
products = await Http.GetFromJsonAsync<List<Product>>("api/products") ?? new();
isLoading = false;
}
}
Blazor anropar StateHasChanged() automatiskt efter en händelsehanterare (t.ex. @onclick). Men om du ändrar tillstånd från en bakgrundstråd eller timer måste du anropa InvokeAsync(StateHasChanged) själv.
Vad är en bakgrundstråd? — Ett program kan köra flera saker parallellt. Varje sådan parallell körning kallas en tråd (thread). Blazors UI-uppdateringar sker alltid på en specifik tråd — "UI-tråden". En bakgrundstråd är en annan tråd som körs parallellt, t.ex. när du startar en System.Threading.Timer eller ett långt Task.Run(...). Koden i timerns callback körs på en bakgrundstråd, inte på UI-tråden.
Problemet: om bakgrundstråden ändrar en C#-variabel och du sedan anropar StateHasChanged() direkt, vet inte Blazor att anropet kom från "fel" tråd och kan krascha eller ignorera uppdateringen. InvokeAsync skickar anropet tillbaka till rätt tråd.
@onclick, @oninput, …) → Blazor sköter StateHasChanged() automatiskt.Timer, Task.Run(), CancellationToken-callbacks, WebSocket-meddelanden o.s.v. → kör await InvokeAsync(StateHasChanged).När en barnkomponent vill berätta något för sin förälder används EventCallback:
@* ProductCard.razor *@
<div class="card">
<h3>@Product.Name</h3>
<p>@Product.Price.ToString("C")</p>
<button @onclick="HandleBuy">Lägg i kundvagn</button>
</div>
@code {
[Parameter, EditorRequired] public Product Product { get; set; } = default!;
[Parameter] public EventCallback<Product> OnBuy { get; set; }
private Task HandleBuy() => OnBuy.InvokeAsync(Product);
}
@* Förälder: Shop.razor *@
@foreach (var p in products)
{
<ProductCard Product="p" OnBuy="AddToCart" />
}
<p>I kundvagn: @cart.Count</p>
@code {
private List<Product> products = new();
private List<Product> cart = new();
private void AddToCart(Product p) => cart.Add(p);
}
Ibland vill man @bind mot ett värde i en barnkomponent. Då behöver barnet både en Value-parameter och en ValueChanged EventCallback:
@* SearchBox.razor *@
<input value="@Value" @oninput="OnInput" placeholder="Sök..." />
@code {
[Parameter] public string Value { get; set; } = "";
[Parameter] public EventCallback<string> ValueChanged { get; set; }
private Task OnInput(ChangeEventArgs e)
=> ValueChanged.InvokeAsync(e.Value?.ToString() ?? "");
}
@* Förälder *@
<SearchBox @bind-Value="searchTerm" />
<p>Du söker efter: @searchTerm</p>
@code { private string searchTerm = ""; }
Blazor renderar om en komponent när:
StateHasChanged() själv (manuellt)private System.Threading.Timer? _timer;
protected override void OnInitialized()
{
_timer = new System.Threading.Timer(_ =>
{
sekunder++;
InvokeAsync(StateHasChanged); // viktig — vi är på en annan tråd
}, null, 0, 1000);
}
@bind, format-strängar och händelser.Lös övningarna självständigt. Det finns inget facit — lärandet sker i processen.
<input> som filtrerar en lista på 20 hårdkodade produkter live medan du skriver. Använd @bind:event="oninput".
await Task.Delay(1500)). Visa en laddningsindikator under tiden och inaktivera knappen.
RatingStars-komponent (1–5 stjärnor) som rapporterar ändrade värden till föräldern via en EventCallback<int>. Föräldern visar det valda värdet.
NumberSpinner-komponent med +/- knappar som föräldern kan @bind-Value mot. Visa två spinners på samma sida och summera deras värden.
CalculatorButton-komponent som rapporterar klick till en föräldrakomponent via EventCallback. Föräldern håller display-state och beräknar resultat.