Blazor 组件生命周期:完整指南
我已经使用 Blazor 一段时间了,老实说,生命周期方法一开始让我感到困惑。 OnInitialized vs OnInitializedAsync? OnParametersSet vs OnAfterRender? StateHasChanged 何时真正触发重新渲染?经过大量的尝试和错误,我终于对这一切有了一个可靠的心理模型。
生命周期概览
当 Blazor 组件呈现时,它会按顺序执行以下方法:
SetParametersAsyncOnInitialized/OnInitializedAsyncOnParametersSet/OnParametersSetAsyncOnAfterRender/OnAfterRenderAsyncDispose/DisposeAsync
让我们逐一介绍一下。
设置参数异步
这是调用的第一个方法。它从父组件接收原始 ParameterView。大多数情况下,您不会覆盖此设置 — Blazor 会自动处理映射参数。但是,如果您需要在分配自定义参数之前进行处理或验证:
public override async Task SetParametersAsync(ParameterView parameters)
{
// Custom logic before parameters are set
if (parameters.TryGetValue<string>("Title", out var title))
{
Console.WriteLine($"Title is being set to: {title}");
}
await base.SetParametersAsync(parameters);
}
我在实际项目中只使用过一次。大多数时候你会跳过它。
OnInitialized / OnInitializedAsync
这是您进行设置工作的地方 - 加载数据、初始化服务、设置默认值。它在组件首次创建时运行一次。
@code {
private List<Product> products = new();
protected override async Task OnInitializedAsync()
{
products = await Http.GetFromJsonAsync<List<Product>>("api/products");
}
}
一件让我困惑的事情:在 Blazor Server 中,OnInitializedAsync 在预渲染期间被调用两次。第一次是在服务器端预渲染期间,第二次是在建立 SignalR 连接时。如果您的 API 调用成本高昂,您可能需要处理:
private bool isPrerendering = true;
protected override async Task OnInitializedAsync()
{
products = await Http.GetFromJsonAsync<List<Product>>("api/products");
isPrerendering = false;
}
或者更好的是,使用 PersistentComponentState 完全避免双重调用。
OnParametersSet / OnParametersSetAsync
每次父组件重新渲染并传递新参数值时都会触发此事件。它也会在 OnInitialized 之后触发。这是对参数更改做出反应的正确位置:
[Parameter]
public int CategoryId { get; set; }
private int previousCategoryId;
private List<Product> products = new();
protected override async Task OnParametersSetAsync()
{
if (CategoryId != previousCategoryId)
{
previousCategoryId = CategoryId;
products = await Http.GetFromJsonAsync<List<Product>>(
$"api/products?category={CategoryId}");
}
}
对 CategoryId != previousCategoryId 的检查很重要 - 如果没有它,每次父级重新渲染时您都会重新加载数据,即使类别没有更改。
OnAfterRender / OnAfterRenderAsync
该事件在组件渲染到 DOM 后触发。 firstRender 参数告诉您这是否是初始渲染。这是 JS Interop 调用的地方,因为此时 DOM 元素已存在:
[Inject]
private IJSRuntime JS { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("initializeChart", chartElement);
}
}
我经常使用 firstRender 来避免每次重新渲染时重新初始化 JavaScript 库。如果您要设置事件侦听器、图表库或任何直接接触 DOM 的内容,那么这就是它所在的位置。
应该渲染
这个不太为人所知,但非常有用。它控制调用 StateHasChanged 时组件是否重新渲染:
private bool shouldRender = true;
protected override bool ShouldRender() => shouldRender;
private void HeavyOperation()
{
shouldRender = false;
// Do a bunch of state changes without triggering renders
for (int i = 0; i < 1000; i++)
{
items[i].Process();
}
shouldRender = true;
StateHasChanged(); // Now render once with all changes
}
我用它来优化处理大型列表的组件。您无需在每个项目更改时重新渲染,而是批量更新并在最后渲染一次。
处置/DisposeAsync
从 UI 中删除组件后,您应该清理所有资源。实施 IDisposable 或 IAsyncDisposable:
@implements IAsyncDisposable
@code {
private Timer? timer;
private IJSObjectReference? jsModule;
protected override void OnInitialized()
{
timer = new Timer(OnTick, null, 0, 1000);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
jsModule = await JS.InvokeAsync<IJSObjectReference>("import", "./timer.js");
}
}
public async ValueTask DisposeAsync()
{
timer?.Dispose();
if (jsModule is not null)
{
await jsModule.DisposeAsync();
}
}
}
要处理的常见内容:计时器、事件订阅、JS 模块引用、CancellationTokenSource 以及您创建的任何 IDisposable 服务。
状态已改变这不是生命周期方法,但密切相关。它告诉 Blazor“嘿,我的状态发生了变化,请重新渲染我。” Blazor 在事件处理程序之后自动调用它,但有时您需要手动调用它 - 通常是当状态从正常 Blazor 事件流外部发生更改时:
private async Task StartPolling()
{
while (!cts.Token.IsCancellationRequested)
{
data = await Http.GetFromJsonAsync<Data>("api/data");
StateHasChanged(); // Manual call needed since this isn't a Blazor event
await Task.Delay(5000, cts.Token);
}
}
一个重要注意事项:如果您从 Blazor Server 中的后台线程进行更新,请使用 InvokeAsync:
await InvokeAsync(() =>
{
data = newData;
StateHasChanged();
});
完整图片
以下是一切发生的顺序:
Component created
└─ SetParametersAsync
└─ OnInitialized / OnInitializedAsync
└─ OnParametersSet / OnParametersSetAsync
└─ Render
└─ OnAfterRender(firstRender: true)
Parameter change from parent
└─ SetParametersAsync
└─ OnParametersSet / OnParametersSetAsync
└─ ShouldRender?
└─ Render
└─ OnAfterRender(firstRender: false)
Component removed
└─ Dispose / DisposeAsync
一旦你脑子里有了这个流程,调试生命周期问题就变得容易多了。
希望你喜欢这篇文章!请随时通过任何社交媒体与我联系:@emimontesdeoca。