StateHasChanged & ShouldRender – Blazor Server
1- ShouldRender – (Tuỳ chọn) ngăn render không cần thiết
ShouldRender() được gọi mỗi khi Blazor chuẩn bị thực hiện một lần render component. Nếu phương thức này trả về false, Blazor sẽ hủy lần render đó.
Trong ví dụ này, nếu bạn bỏ chọn checkbox phía trên,
component sẽ không render lại ngay cả khi có gọi StateHasChanged()
hoặc InvokeAsync(StateHasChanged).
Điều này có nghĩa là dữ liệu trong component có thể đã thay đổi, nhưng giao diện (UI) sẽ không được cập nhật. Thiết lập này có thể ảnh hưởng đến các ví dụ phía sau, vì tất cả đều phụ thuộc vào cơ chế render của component.
Xem code
<input class="form-check-input" type="checkbox" id="chkShouldRender" @bind="EnableShouldRenderFilter" />
@code
{
bool EnableShouldRenderFilter = true;
protected override bool ShouldRender()
{
return EnableShouldRenderFilter;
}
}
📌 Lưu ý khi xem các ví dụ về StateHasChanged
Mỗi lần gọi StateHasChanged() hoặc
InvokeAsync(StateHasChanged), Blazor sẽ yêu cầu
render lại toàn bộ component/page hiện tại
(nếu ShouldRender() trả về true),
không chỉ riêng phần ví dụ đang thao tác.
Sau khi render, Blazor chỉ cập nhật DOM ở những vị trí có dữ liệu thay đổi.
- Các ví dụ trong trang có thể ảnh hưởng lẫn nhau (render “ké”).
- Khi thử một ví dụ, hãy bật filter của ví dụ đó.
- Tắt filter của các ví dụ còn lại để tránh nhầm lẫn.
- Nếu đã gọi
StateHasChanged()mà UI không cập nhật, hãy kiểm tra lạiShouldRender().
2- Async Task (UI event) – dùng StateHasChanged trong cùng UI Context
📌 Hành vi render của @onclick trong Blazor
- Không gán sự kiện → không có yêu cầu render.
- Handler đồng bộ (không trả về
Taskchưa hoàn tất) → render 1 lần sau khi handler kết thúc. -
Handler bất đồng bộ (
asyncvà cóawaitlàmTaskchưa hoàn tất) → render 2 lần:- Sau khi phần thực thi đồng bộ ban đầu hoàn tất (trước khi
awaityield). - Khi
Taskcủa handler hoàn tất.
- Sau khi phần thực thi đồng bộ ban đầu hoàn tất (trước khi
- Mỗi lần render là render lại component, nhưng Blazor chỉ cập nhật DOM ở những vị trí có thay đổi.
Lưu ý: Ví dụ này chạy trong UI synchronization context
(do được gọi từ @onclick), vì vậy khi cần render giữa chừng
chỉ cần gọi StateHasChanged().
Không cần dùng InvokeAsync(StateHasChanged).
Trong ví dụ RunAsync, nếu có await thì mặc định sẽ render 2 lần.
Nếu bật tùy chọn EnableStateHasChanged và gọi
StateHasChanged() giữa các await,
sẽ phát sinh thêm một lần render ở giữa.
Service value: 0
Xem code
@inject CounterService counterService
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="chkAsyncShc" @bind="EnableStateHasUIContext" />
<label class="form-check-label" for="chkAsyncShc">Enable StateHasChanged in UI context</label>
</div>
<button class="btn btn-warning" Ư@onclick="RunAsync">RunAsync</button>
<p class="mt-2"><b>Service value:</b> @counterService.CurrentCount</p>
@code{
bool EnableStateHasChangedUIContext = false;
private async Task RunAsync()
{
await Task.Delay(1000);
if (EnableStateHasChangedUIContext)
StateHasChanged();
await Task.Delay(1000);
}
}
namespace BlazorSample.Services
{
public class CounterService : IDisposable
{
public int CurrentCount { get; private set; } = 0;
private Timer? _timer;
public CounterService()
{
_timer = new Timer(_ =>
{
CurrentCount++;
if (CurrentCount >= int.MaxValue)
{
CurrentCount = 0;
}
NotifyStateChanged();
},
null,
500, // delay lần đầu
500); // mỗi 1s
}
public event Action? OnChange;
private void NotifyStateChanged() => OnChange?.Invoke();
public void Dispose()
{
_timer?.Dispose();
}
}
}
//DI Service
builder.Services.AddSingleton<BlazorSample.Services.CounterService>();
3- Service bắn Event – dùng InvokeAsync(StateHasChanged)
Service value: 0
Service event chạy ngoài UI synchronization context → phải InvokeAsync(StateHasChanged) để marshal về UI thread rồi mới render.
Xem code
@inject CounterService counterService
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="chkSvcInvoke" @bind="EnableStateHasChanged_Service" />
<label class="form-check-label" for="chkSvcInvoke">Enable StateHasChanged </label>
</div>
<p class="mt-2"> <b>Service value:</b> @counterService.CurrentCount</p>
@code{
bool EnableStateHasChanged_Service = false;
//Đăng ký OnServiceTick vào Event OnChange từ service.
protected override void OnInitialized()
{
counterService.OnChange += OnServiceTick;
}
//Callback từ service mỗi khi có thay đổi
void OnServiceTick()
{
// ✅ Callback từ service có thể đến từ background thread
// => phải InvokeAsync để quay về UI context
if (EnableStateHasChanged_Service)
InvokeAsync(StateHasChanged);
}
//Hủy đăng ký khi component bị dispose để tránh memory leak
public void Dispose()
{
counterService.OnChange -= OnServiceTick;
}
}
namespace BlazorSample.Services
{
public class CounterService : IDisposable
{
public int CurrentCount { get; private set; } = 0;
private Timer? _timer;
public CounterService()
{
_timer = new Timer(_ =>
{
CurrentCount++;
if (CurrentCount >= int.MaxValue)
{
CurrentCount = 0;
}
NotifyStateChanged();
},
null,
500, // delay lần đầu
500); // mỗi 1s
}
public event Action? OnChange;
private void NotifyStateChanged() => OnChange?.Invoke();
public void Dispose()
{
_timer?.Dispose();
}
}
}
//DI Service
builder.Services.AddSingleton<BlazorSample.Services.CounterService>();
4- Timer – dùng InvokeAsync(StateHasChanged)
Time:
Timer chạy ngoài UI synchronization context → phải InvokeAsync(StateHasChanged) để marshal về UI thread rồi mới render.
Xem code
@inject CounterService counterService
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="chkSvcInvoke" @bind="EnableStateHasChanged_Service" />
<label class="form-check-label" for="chkSvcInvoke">Enable StateHasChanged </label>
</div>
<p class="mt-2"> <b>Time:</b> @_time</p>
@code{
bool EnableStateHasChanged_Timer = false;
Timer? _timer;
string _time = "";
//Khởi tạo Timer trong OnInitialized
protected override void OnInitialized()
{
// Timer (background thread)
_timer = new Timer(_ =>
{
_time = DateTime.Now.ToString("HH:mm:ss");
if (EnableStateHasChanged_Timer)
InvokeAsync(StateHasChanged);
}, null, 0, 1000);
}
//Hủy Timer khi component bị dispose để tránh memory leak
public void Dispose()
{
_timer?.Dispose();
}
}
📌 Tổng quan về StateHasChanged() trong Blazor
StateHasChanged() là phương thức dùng để thông báo cho Blazor rằng
trạng thái (state) của component đã thay đổi và cần được render lại.
- Khi được gọi, Blazor sẽ đưa component vào hàng đợi render.
-
Trước khi render thực sự, Blazor sẽ gọi
ShouldRender(). Nếu phương thức này trả vềfalse, lần render sẽ bị hủy. - Render trong Blazor nghĩa là xây dựng lại render tree của component, sau đó so sánh (diff) với cây cũ và chỉ cập nhật DOM ở những vị trí có thay đổi.
Lưu ý quan trọng:
StateHasChanged()chỉ an toàn khi đang ở UI synchronization context.-
Nếu gọi từ Service, Timer hoặc background thread,
cần sử dụng
InvokeAsync(StateHasChanged)để đưa yêu cầu render về đúng UI thread. - Blazor có thể gộp (batch) nhiều yêu cầu render thành một lần thực thi thực tế.
Tóm lại, StateHasChanged() không trực tiếp “vẽ lại giao diện”,
mà chỉ yêu cầu Blazor thực hiện một lần render an toàn và có kiểm soát.