ASP.NET Core Blazor 生命周期ASP.NET Core Blazor lifecycle

本文内容

作者:Luke LathamDaniel Roth

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

生命周期方法Lifecycle methods

组件初始化方法Component initialization methods

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

对于同步操作,替代 OnInitialized

  1. protected override void OnInitialized()
  2. {
  3. ...
  4. }

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

  1. protected override async Task OnInitializedAsync()
  2. {
  3. await ...
  4. }

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

  • 在组件最初作为页面的一部分静态呈现时调用一次。
  • 在浏览器重新建立与服务器的连接时调用第二次。

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

在 Blazor Server 应用进行预呈现时,由于尚未建立与浏览器的连接,无法执行特定操作(例如调用 JavaScript)。预呈现时,组件可能需要进行不同的呈现。有关详细信息,请参阅检测应用何时预呈现部分。

设置参数之前Before parameters are set

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

  1. public override async Task SetParametersAsync(ParameterView parameters)
  2. {
  3. await ...
  4. await base.SetParametersAsync(parameters);
  5. }

每次调用 SetParametersAsync 时,ParameterView 都包含整个参数值集。

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

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

设置参数之后After parameters are set

OnParametersSetAsyncOnParametersSet 在以下情况下调用:

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

备注

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

  1. protected override void OnParametersSet()
  2. {
  3. ...
  4. }

组件呈现之后After component render

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

OnAfterRenderAsyncOnAfterRenderfirstRender 参数:

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

备注

呈现后立即进行的异步操作必须在 OnAfterRenderAsync 生命周期事件期间发生。

即使从 OnAfterRenderAsync 返回 Task,框架也不会在任务完成后为组件再安排一个呈现循环。这是为了避免无限呈现循环。它与其他生命周期方法不同,后者在返回的任务完成后会再安排呈现循环。

  1. protected override void OnAfterRender(bool firstRender)
  2. {
  3. if (firstRender)
  4. {
  5. ...
  6. }
  7. }

在服务器上进行预呈现时未调用 OnAfterRenderOnAfterRenderAsync

禁止 UI 刷新Suppress UI refreshing

替代 ShouldRender 以禁止 UI 刷新。如果实现返回 true,则刷新 UI:

  1. protected override bool ShouldRender()
  2. {
  3. var renderUI = true;
  4. return renderUI;
  5. }

每次呈现组件时都会调用 ShouldRender

即使 ShouldRender 被替代,组件也始终在最初呈现。

状态更改State changes

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

处理呈现时的不完整异步操作Handle incomplete async actions at render

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

在 Blazor 模板的 FetchData 组件中,替代 OnInitializedAsync 以异步接收预测数据 (forecasts)。forecastsnull 时,将向用户显示加载消息。OnInitializedAsync 返回的 Task 完成后,该组件以更新后的状态重新呈现。

Blazor Server 模板中的 Pages/FetchData.razor

  1. @page "/fetchdata"
  2. @using MyBlazorApp.Data
  3. @inject WeatherForecastService ForecastService
  4. <h1>Weather forecast</h1>
  5. <p>This component demonstrates fetching data from a service.</p>
  6. @if (_forecasts == null)
  7. {
  8. <p><em>Loading...</em></p>
  9. }
  10. else
  11. {
  12. <table class="table">
  13. <!-- forecast data in table element content -->
  14. </table>
  15. }
  16. @code {
  17. private WeatherForecast[] _forecasts;
  18. protected override async Task OnInitializedAsync()
  19. {
  20. _forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
  21. }
  22. }

使用 IDisposable 处置组件Component disposal with IDisposable

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

  1. @using System
  2. @implements IDisposable
  3. ...
  4. @code {
  5. public void Dispose()
  6. {
  7. ...
  8. }
  9. }

备注

不支持在 Dispose 中调用 StateHasChangedStateHasChanged 可能在拆除呈现器时调用,因此不支持在此时请求 UI 更新。

处理错误Handle errors

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

预呈现后的有状态重新连接Stateful reconnection after prerendering

在 Blazor Server 应用中,当 RenderModeServerPrerendered 时,组件最初作为页面的一部分静态呈现。浏览器重新建立与服务器的连接后,将再次呈现组件,并且该组件现在为交互式。如果存在用于初始化组件的 OnInitialized{Async} 生命周期方法,则该方法执行两次

  • 在静态预呈现组件时执行一次。
  • 在建立服务器连接后执行一次。

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

若要避免 Blazor Server 应用中出现双重呈现,请执行以下操作:

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

以下代码演示基于模板的 Blazor Server 应用中更新后的 WeatherForecastService,其避免了双重呈现:

  1. public class WeatherForecastService
  2. {
  3. private static readonly string[] _summaries = new[]
  4. {
  5. "Freezing", "Bracing", "Chilly", "Cool", "Mild",
  6. "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
  7. };
  8. public WeatherForecastService(IMemoryCache memoryCache)
  9. {
  10. MemoryCache = memoryCache;
  11. }
  12. public IMemoryCache MemoryCache { get; }
  13. public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
  14. {
  15. return MemoryCache.GetOrCreateAsync(startDate, async e =>
  16. {
  17. e.SetOptions(new MemoryCacheEntryOptions
  18. {
  19. AbsoluteExpirationRelativeToNow =
  20. TimeSpan.FromSeconds(30)
  21. });
  22. var rng = new Random();
  23. await Task.Delay(TimeSpan.FromSeconds(10));
  24. return Enumerable.Range(1, 5).Select(index => new WeatherForecast
  25. {
  26. Date = startDate.AddDays(index),
  27. TemperatureC = rng.Next(-20, 55),
  28. Summary = _summaries[rng.Next(_summaries.Length)]
  29. }).ToArray();
  30. });
  31. }
  32. }

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

检测应用何时预呈现Detect when the app is prerendering

在 Blazor 服务器应用进行预呈现时,由于尚未建立与浏览器的连接,无法执行调用 JavaScript 等特定操作。预呈现时,组件可能需要进行不同的呈现。

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

  1. @using Microsoft.JSInterop
  2. @inject IJSRuntime JSRuntime
  3. <div @ref="divElement">Text during render</div>
  4. @code {
  5. private ElementReference divElement;
  6. protected override async Task OnAfterRenderAsync(bool firstRender)
  7. {
  8. if (firstRender)
  9. {
  10. await JSRuntime.InvokeVoidAsync(
  11. "setElementText", divElement, "Text after render");
  12. }
  13. }
  14. }

对于上述示例代码,请在 wwwroot/index.html<head> ( WebAssembly) 或 Pages/Host.cshtmlBlazor (_ 服务器)的 setElementText 元素中,提供了一个 Blazor JavaScript 函数。该函数通过 IJSRuntime.InvokeVoidAsync 进行调用,不返回值:

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

警告

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

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

如果调用 JSRuntime.InvokeAsync,则 ElementRef 仅在 OnAfterRenderAsync 中使用,而不在任何更早的生命周期方法中使用,因为呈现组件后才会有 JavaScript 元素。

会调用 StateHasChanged,使用从 JavaScript 互操作调用中获取的新状态重新呈现该组件。此代码不会创建无限循环,因为仅在 infoFromJsnull 时才调用 StateHasChanged

  1. @page "/prerendered-interop"
  2. @using Microsoft.AspNetCore.Components
  3. @using Microsoft.JSInterop
  4. @inject IJSRuntime JSRuntime
  5. <p>
  6. Get value via JS interop call:
  7. <strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
  8. </p>
  9. Set value via JS interop call:
  10. <div id="val-set-by-interop" @ref="divElement"></div>
  11. @code {
  12. private string infoFromJs;
  13. private ElementReference divElement;
  14. protected override async Task OnAfterRenderAsync(bool firstRender)
  15. {
  16. if (firstRender && infoFromJs == null)
  17. {
  18. infoFromJs = await JSRuntime.InvokeAsync<string>(
  19. "setElementText", divElement, "Hello from interop call!");
  20. StateHasChanged();
  21. }
  22. }
  23. }

对于上述示例代码,请在 wwwroot/index.html<head> ( WebAssembly) 或 Pages/Host.cshtmlBlazor (_ 服务器)的 setElementText 元素中,提供了一个 Blazor JavaScript 函数。该函数通过 IJSRuntime.InvokeAsync 进行调用,会返回值:

  1. <script>
  2. window.setElementText = (element, text) => {
  3. element.innerText = text;
  4. return text;
  5. };
  6. </script>

警告

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