Blazor 组件生命周期:完整指南

· 3 分钟阅读

我已经使用 Blazor 一段时间了,老实说,生命周期方法一开始让我感到困惑。 OnInitialized vs OnInitializedAsyncOnParametersSet vs OnAfterRenderStateHasChanged 何时真正触发重新渲染?经过大量的尝试和错误,我终于对这一切有了一个可靠的心理模型。

生命周期概览

当 Blazor 组件呈现时,它会按顺序执行以下方法:

  1. SetParametersAsync
  2. OnInitialized / OnInitializedAsync
  3. OnParametersSet / OnParametersSetAsync
  4. OnAfterRender / OnAfterRenderAsync
  5. Dispose / 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 中删除组件后,您应该清理所有资源。实施 IDisposableIAsyncDisposable

@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

资源