生命周期

ASP.NET Core Blazor 生命周期

作者: Luke LathamDaniel Roth

Blazor 框架包括同步和异步生命周期方法。 重写生命周期方法,以便在组件初始化和呈现期间对组件执行其他操作。

生命周期方法

组件初始化方法

当组件从其父组件收到其初始参数后,将调用 OnInitializedAsyncOnInitialized 当组件执行异步操作时,请使用 OnInitializedAsync,并在操作完成时进行刷新。

对于同步操作,请重写 OnInitialized

protected override void OnInitialized()
{
    ...
}

若要执行异步操作,请重写 OnInitializedAsync,并对操作使用 await 关键字:

protected override async Task OnInitializedAsync()
{
    await ...
}

Blazor Server apps 预呈现其内容调用 OnInitializedAsync 两次

  • 作为页的一部分,最初以静态方式呈现组件时。
  • 第二次当浏览器与服务器建立连接时。

若要防止 OnInitializedAsync 中的开发人员代码运行两次,请参阅预呈现后的有状态重新连接部分。

在预呈现 Blazor 服务器应用时,某些操作(如调用 JavaScript)不可能,因为尚未建立与浏览器的连接。 在预呈现时,组件可能需要以不同的方式呈现。 有关详细信息,请参阅检测应用程序何时预呈现部分。

设置参数之前

SetParametersAsync 设置由呈现树中的组件父级提供的参数:

public override async Task SetParametersAsync(ParameterView parameters)
{
    await ...

    await base.SetParametersAsync(parameters);
}

ParameterView 包含每次调用 SetParametersAsync 时的完整参数值集。

SetParametersAsync 的默认实现将每个属性的值设置为在 ParameterView中具有相应值的 [Parameter][CascadingParameter] 特性。 ParameterView 中没有相应值的参数将保持不变。

如果未调用 base.SetParametersAync,则自定义代码可以通过任何所需的方式解释传入参数值。 例如,不要求将传入参数分配给类的属性。

设置参数后

调用 OnParametersSetAsyncOnParametersSet

  • 当组件已初始化并收到其父组件的第一组参数时。
  • 当父组件重新呈现并提供时:
    • 仅有至少一个参数发生更改的已知基元不可变类型。
    • 任何复杂类型的参数。 此框架无法知道复杂类型参数的值是否在内部转变,因此它将参数集视为已更改。
protected override async Task OnParametersSetAsync()
{
    await ...
}

备注

应用参数和属性值时的异步工作必须在 OnParametersSetAsync 生命周期事件期间发生。

protected override void OnParametersSet()
{
    ...
}

组件呈现后

当组件完成呈现后,将调用 OnAfterRenderAsyncOnAfterRender 此时将填充元素和组件引用。 使用此阶段,可以使用呈现的内容来执行其他初始化步骤,如激活在呈现的 DOM 元素上操作的第三方 JavaScript 库。

OnAfterRenderAsyncOnAfterRenderfirstRender 参数:

  • 在第一次呈现组件实例时,设置为 true
  • 可用于确保仅执行一次初始化工作。
protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        await ...
    }
}

备注

OnAfterRenderAsync 生命周期事件期间,必须立即进行渲染后的异步工作。

即使从 OnAfterRenderAsync中返回 Task,该任务完成后,框架也不会为组件计划进一步的呈现循环。 这是为了避免出现无限的呈现循环。 它与其他生命周期方法不同,后者将在返回的任务完成后计划更多的渲染循环。

protected override void OnAfterRender(bool firstRender)
{
    if (firstRender)
    {
        ...
    }
}

服务器上预呈现时不会调用OnAfterRenderOnAfterRenderAsync

禁止 UI 刷新

重写 ShouldRender 以取消 UI 刷新。 如果实现返回 true,则将刷新 UI:

protected override bool ShouldRender()
{
    var renderUI = true;

    return renderUI;
}

每次呈现组件时,都将调用 ShouldRender

即使 ShouldRender 被重写,组件也始终是最初呈现的。

状态更改

StateHasChanged 通知组件状态已更改。 如果适用,调用 StateHasChanged 会导致组件重新呈现。

在呈现时处理未完成的异步操作

在呈现组件之前,在生命周期事件中执行的异步操作可能尚未完成。 在执行生命周期方法时,对象可能 null 或未完全填充数据。 提供呈现逻辑以确认对象已初始化。 null对象时呈现占位符 UI 元素(例如,加载消息)。

在 Blazor 模板的 FetchData 组件中,OnInitializedAsync 被重写为 asychronously 接收预测数据(forecasts)。 null``forecasts 时,将向用户显示一条加载消息。 OnInitializedAsyncTask 返回完成后,组件将重新呈现已更新状态。

页面/FetchData Blazor 服务器模板:

@page "/fetchdata"
@using MyBlazorApp.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (_forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <!-- forecast data in table element content -->
    </table>
}

@code {
    private WeatherForecast[] _forecasts;

    protected override async Task OnInitializedAsync()
    {
        _forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

用 IDisposable 进行的组件处置

如果某个组件实现 IDisposable,则在从 UI 中删除该组件时,将调用Dispose 方法 以下组件使用 @implements IDisposableDispose 方法:

@using System
@implements IDisposable

...

@code {
    public void Dispose()
    {
        ...
    }
}

备注

不支持在 Dispose 中调用 StateHasChanged StateHasChanged 可以作为撕裂渲染器的一部分被调用,因此不支持在该点请求 UI 更新。

处理错误

有关在执行生命周期方法期间处理错误的信息,请参阅 处理 ASP.NET Core Blazor 应用中的错误

预呈现后有状态重新连接

RenderMode ServerPrerendered时,在 Blazor Server 应用程序中,最初以页面的形式呈现组件。 一旦浏览器与服务器建立了连接,该组件将再次呈现,并且该组件现在是交互式的。 如果存在用于初始化组件的OnInitialized {Async}生命周期方法,则将执行两次此方法:

  • 如果组件预呈现静态,则为。
  • 建立服务器连接之后。

这可能会导致在最终呈现组件时,UI 中显示的数据发生显著变化。

若要避免 Blazor 服务器应用中出现双重渲染方案:

  • 传入一个标识符,该标识符可用于在预呈现期间缓存状态并在应用重新启动后检索状态。
  • 在预呈现期间使用标识符以保存组件状态。
  • 在预呈现后使用标识符检索缓存状态。

下面的代码演示基于模板的 Blazor 服务器应用中的更新 WeatherForecastService,可避免双重呈现:

public class WeatherForecastService
{
    private static readonly string[] _summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };
    
    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }
    
    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = 
                    TimeSpan.FromSeconds(30)
            });

            var rng = new Random();

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = _summaries[rng.Next(_summaries.Length)]
            }).ToArray();
        });
    }
}

有关 RenderMode的详细信息,请参阅 ASP.NET Core Blazor 宿主模型配置

检测预呈现应用的时间

在预呈现 Blazor 服务器应用时,某些操作(如调用 JavaScript)不可能,因为尚未建立与浏览器的连接。 在预呈现时,组件可能需要以不同的方式呈现。

若要在建立与浏览器的连接后延迟 JavaScript 互操作调用,可以使用OnAfterRenderAsync 组件生命周期事件 仅在完全呈现应用并建立客户端连接后,才调用此事件。

@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JSRuntime.InvokeVoidAsync(
                "setElementText", divElement, "Text after render");
        }
    }
}

对于前面的示例代码,请在wwwroot/index.html (Blazor WebAssembly)或Pages/_Host (Blazor Server)的 <head> 元素内提供 setElementText JavaScript 函数。 使用 IJSRuntime.InvokeVoidAsync 调用函数,并且不返回值:

<script>
  window.setElementText = (element, text) => element.innerText = text;
</script>

警告

前面的示例仅修改了文档对象模型(DOM),以便仅用于演示目的。 大多数情况下不建议使用 JavaScript 直接修改 DOM,因为 JavaScript 可能会干扰 Blazor的更改跟踪。

以下组件演示了如何以与预呈现兼容的方式将 JavaScript 互操作作为组件的初始化逻辑的一部分使用。 该组件显示可以从 OnAfterRenderAsync内触发呈现更新。 开发人员必须避免在此方案中创建无限循环。

在调用 JSRuntime.InvokeAsync 的情况下,ElementRef 仅用于 OnAfterRenderAsync,而不是在任何早期的生命周期方法中,因为在呈现组件之前,不会有 JavaScript 元素。

调用StateHasChanged以诸如此类组件,并将新状态从 JavaScript 互操作调用中获取。 此代码不会创建无限循环,因为仅当 infoFromJs null时才会调用 StateHasChanged

@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
</p>

Set value via JS interop call:
<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string infoFromJs;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && infoFromJs == null)
        {
            infoFromJs = await JSRuntime.InvokeAsync<string>(
                "setElementText", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

对于前面的示例代码,请在wwwroot/index.html (Blazor WebAssembly)或Pages/_Host (Blazor Server)的 <head> 元素内提供 setElementText JavaScript 函数。 使用 IJSRuntime.InvokeAsync 调用函数并返回值:

<script>
  window.setElementText = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

警告

前面的示例仅修改了文档对象模型(DOM),以便仅用于演示目的。 大多数情况下不建议使用 JavaScript 直接修改 DOM,因为 JavaScript 可能会干扰 Blazor的更改跟踪。