BÀI 07Component và trạng thái
State Management
Giữ một nguồn dữ liệu chung, thông báo khi dữ liệu thay đổi và cập nhật các phần giao diện đang sử dụng nó.
Giải thích, ví dụ & thực hành
Tất cả bài học
Các khu vực trên trang cùng đọc một CounterStateService.
Khi bạn thay đổi bộ đếm, service phát OnChange để component biết cần cập nhật giao diện.
Thử ngay. Bấm tăng hoặc giảm bộ đếm, rồi đối chiếu khu vực chỉ đọc và bảng log.
Chú ý: dữ liệu nằm trong service; giao diện chỉ đọc dữ liệu đó và phản ứng với thông báo thay đổi.
01. Thay đổi dữ liệu chung
Count: 0
LastAction: none
Xem code
Razor
@inject CounterStateService CounterState
<button class="btn btn-primary" @onclick="Increase">+ Increment</button>
<button class="btn btn-warning" @onclick="Decrease">- Decrement</button>
<button class="btn btn-danger" @onclick="ResetState">Reset</button>
<p><b>Count:</b> @CounterState.Count</p>
<p><b>LastAction:</b> @CounterState.LastAction</p>
C#
private void Increase()
{
CounterState.Increment();
}
private void Decrease()
{
CounterState.Decrement();
}
private void ResetState()
{
CounterState.Reset();
}
02. Đọc cùng một nguồn dữ liệu
Shared Count: 0
Shared LastAction: none
Khu vực này không tự thay đổi state, chỉ đọc từ cùng một service DI. Khi khu vực trên bấm nút, phần này cũng cập nhật theo.
Xem code
@inject CounterStateService CounterState
<p><b>Shared Count:</b> @CounterState.Count</p>
<p><b>Shared LastAction:</b> @CounterState.LastAction</p>
03. Theo dõi sự kiện OnChange
Render Count: 0
State Change Count: 0
Logs:
Xem code
Razor
<p><b>Render Count:</b> @_renderCount</p>
<p><b>State Change Count:</b> @_stateChangeCount</p>
<ul>
@foreach (var item in _logs)
{
<li>@item</li>
}
</ul>
<button class="btn btn-outline-secondary" @onclick="ClearLogs">
Clear Logs
</button>
C#
protected override void OnInitialized()
{
CounterState.OnChange += HandleStateChanged;
}
private void HandleStateChanged()
{
_stateChangeCount++;
_logs.Add(
$"Count = {CounterState.Count}, Action = {CounterState.LastAction}"
);
InvokeAsync(StateHasChanged);
}
public void Dispose()
{
CounterState.OnChange -= HandleStateChanged;
}
protected override void OnAfterRender(bool firstRender)
{
_renderCount++;
}
04. Vì sao cần OnChange?
- Service đổi state không tự làm UI render lại.
- Service chỉ nên giữ dữ liệu và bắn sự kiện thông báo thay đổi.
- Component lắng nghe
OnChangerồi gọiInvokeAsync(StateHasChanged). -
Dùng
InvokeAsync(StateHasChanged)an toàn hơn vì callback có thể đến từ ngoài UI context.
05. Mã nguồn của service
Xem code service
public class CounterStateService
{
public int Count { get; private set; } = 0;
public string LastAction { get; private set; } = "none";
public event Action? OnChange;
public void Increment()
{
Count++;
LastAction = $"Increment at {DateTime.Now:HH:mm:ss}";
NotifyStateChanged();
}
public void Decrement()
{
Count--;
LastAction = $"Decrement at {DateTime.Now:HH:mm:ss}";
NotifyStateChanged();
}
public void Reset()
{
Count = 0;
LastAction = $"Reset at {DateTime.Now:HH:mm:ss}";
NotifyStateChanged();
}
private void NotifyStateChanged()
=> OnChange?.Invoke();
}
Điểm cần nhớ
Service giữ dữ liệu, component chịu trách nhiệm cập nhật giao diện.
Đăng ký sự kiện khi component bắt đầu sử dụng service và hủy đăng ký trong Dispose khi rời trang.