BÀI 05Component và trạng thái
Lifecycle
Theo dõi vòng đời của component: khởi tạo, nhận tham số, render và giải phóng tài nguyên.
Giải thích, ví dụ & thực hành
Tất cả bài học
Thử và quan sát. Bấm Value++ để gửi tham số mới xuống component con.
Dùng Toggle Child để ẩn/hiện lại component, rồi so sánh thứ tự trong bảng log.
Các lựa chọn bên dưới giúp bạn thấy khác biệt khi một hàm có
await hoặc khi bật ShouldRender.
01. Thiết lập lần thử
Parent Value: 0
Khi component con được gỡ khỏi trang, Dispose là nơi hủy đăng ký sự kiện và giải phóng tài nguyên mà nó đã tạo.
02. Kết quả từ component con
Child Value: 0(EnableFilter: False)
Child Logs:
Điểm cần nhớ
OnInitializedchạy khi một instance component mới được tạo.OnParametersSetgiúp xử lý bộ tham số vừa nhận từ component cha.ShouldRenderquyết định có cập nhật phần giao diện ở lượt đó hay không.
03. Mã nguồn để đối chiếu
Xem code
Parent page(LifecycleDemo.razor)
@page "/lifecycle-demo"
<div class="border rounded p-3">
@*Cài đặt tham số cho LifeCycle của child Component*@
<div class="form-check ms-2">
<input class="form-check-input" type="checkbox" id="chkOI" @bind="_useAwaitInChild_OnInitializedAsync"/>
<label class="form-check-label" for="chkOI">Chạy Await trong hàm OnInitializedAsync của Child Component</label>
</div>
<div class="form-check ms-2">
<input class="form-check-input" type="checkbox" id="chkOP" @bind="_useAwaitInChild_OnParametersSetAsync"/>
<label class="form-check-label" for="chkOP">Chạy Await trong hàm OnParametersSetAsync của Child Component</label>
</div>
<div class="form-check ms-2">
<input class="form-check-input" type="checkbox" id="chkOA" @bind="_useAwaitInChild_OnAfterRenderAsync"/>
<label class="form-check-label" for="chkOA">Chạy Await trong hàm OnAfterRenderAsync của Child Component</label>
</div>
<div class="form-check ms-2">
<input class="form-check-input" type="checkbox" id="chkES" @bind="_enableChildShouldRenderFilter"/>
<label class="form-check-label" for="chkES">Bật bộ lọc ShouldRender của Child Component</label>
</div>
@*Tham số truyền vào child component*@
<div class="d-flex align-items-center gap-2 flex-wrap">
<button class="btn btn-primary" @onclick="Inc">Value++</button>
<button class="btn btn-outline-secondary" @onclick="ToggleChild">Toggle Child (mount/unmount)</button>
</div>
<p class="mt-3 mb-2"><b>Parent Value:</b> @_value</p>
</div>
<span class="text-muted">*Lưu ý: Khi child component bị unmount thì sẽ gọi đến Dispose (nếu có) → có thể dùng để cleanup resource, hủy subscription,...*</span>
<div class="border rounded p-3">
@if (_showChild)
{
<div>
<LifecycleChild Value="@_value" EnableShouldRenderFilter="@_enableChildShouldRenderFilter"
UseAwaitInOnInitializedAsync="@_useAwaitInChild_OnInitializedAsync"
UseAwaitInOnParametersSetAsync="@_useAwaitInChild_OnParametersSetAsync"
UseAwaitInOnAfterRenderAsync="@_useAwaitInChild_OnAfterRenderAsync"/>
</div>
}
else
{
<p class="text-muted mb-0">Child đang bị unmount → nếu trước đó có <code>Dispose</code> thì sẽ chạy.</p>
}
</div>
@code{
private bool _useAwaitInChild_OnInitializedAsync = false;
private bool _useAwaitInChild_OnParametersSetAsync = true;
private bool _useAwaitInChild_OnAfterRenderAsync = false;
private bool _enableChildShouldRenderFilter = false;
private int _value = 0;
private bool _showChild = true;
private void Inc()
{
_value++;
}
private void ToggleChild()
{
_showChild = !_showChild;
}
}
Child Component (LifecycleChild.razor)
@implements IDisposable
<div class="border rounded p-3 bg-light">
<p class="mb-1"><b>Child Value:</b> @Value<span class="text-muted">(EnableFilter: @EnableShouldRenderFilter)</span></p>
<div>
<b>Child Logs:</b>
<ul class="mb-0">
@foreach (var line in _displayLogs)
{
<li>@line</li>
}
</ul>
<button class="btn btn-sm btn-outline-secondary mt-2" @onclick="() => _displayLogs = new List<string>(_logs)">Refresh Logs</button>
<button class="btn btn-sm btn-outline-secondary mt-2" @onclick="() => _clearLogsBeforeNextRender = true">Clear Logs</button>
</div>
</div>
@code
{
[Parameter] public int Value { get; set; }
[Parameter] public bool UseAwaitInOnInitializedAsync { get; set; }
[Parameter] public bool UseAwaitInOnParametersSetAsync { get; set; }
[Parameter] public bool UseAwaitInOnAfterRenderAsync { get; set; }
[Parameter] public bool EnableShouldRenderFilter { get; set; }
private bool _clearLogsBeforeNextRender = false;
private List<string> _logs = new();
private List<string> _displayLogs = new();
private int _renderCount = 0;
public override async Task SetParametersAsync(ParameterView parameters)
{
Log("Child-SetParametersAsync() - start");
await base.SetParametersAsync(parameters);
Log("Child-SetParametersAsync() - end");
}
protected override void OnInitialized()
{
Log("Child-OnInitialized()");
}
protected override async Task OnInitializedAsync()
{
Log("Child-OnInitializedAsync() - start");
if (UseAwaitInOnInitializedAsync)
{
await Task.Delay(1);
}
Log("Child-OnInitializedAsync() - end");
}
protected override void OnParametersSet()
{
Log($"Child-OnParametersSet() - Value={Value}");
}
protected override async Task OnParametersSetAsync()
{
Log("Child-OnParametersSetAsync() - start");
if (UseAwaitInOnParametersSetAsync)
{
await Task.Delay(1);
}
Log("Child-OnParametersSetAsync() - end");
}
protected override bool ShouldRender()
{
Log("Child-ShouldRender()");
if (!EnableShouldRenderFilter)
{
return true;
}
return Value % 2 == 0; // chỉ render khi Value chẵn
}
protected override void OnAfterRender(bool firstRender)
{
Log($"Child-OnAfterRender(firstRender: {firstRender})");
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
Log($"Child-OnAfterRenderAsync(firstRender: {firstRender}) - start");
if (UseAwaitInOnAfterRenderAsync)
{
await Task.Delay(1);
}
Log($"Child-OnAfterRenderAsync(firstRender: {firstRender}) - end");
if (_clearLogsBeforeNextRender)
{
_logs.Clear();
_displayLogs.Clear();
_clearLogsBeforeNextRender = false;
}
}
public void Dispose()
{
Console.WriteLine($"Child-Dispose() {DateTime.Now:HH:mm:ss.fff}");
}
private void Log(string msg)
=> _logs.Add($"{DateTime.Now:HH:mm:ss.fff} - {msg}");
}