Blazor 组件生命周期实例说明

发布时间 2023-07-25 01:54:31作者: AlexChow

1.简介

Blazor的生命周期与React组件的生命周期类似,也分为三个阶段:初始化阶段、运行中阶段和销毁阶段,其相关方法有10个,包括设置参数前、初始化、设置参数之后、组件渲染后以及组件的销毁,但是这些方法有些是重复的,只不过是同步与异步的区别。本文直接实例说明 Blazor 的生命周期

Blazor生命周期方法主要包括:

1 设置参数前 SetParametersAsync
2 初始化 OnInitialized/OnInitializedAsync
3 设置参数后 OnParametersSet/OnParametersSetAsync
4 组件渲染呈现后 OnAfterRender/OnAfterRenderAsync
5 判断是否渲染组件 ShouldRender
6 组件删除前 Dispose
7 通知组件渲染 StateHasChanged

需要注意的点:

(1)OnAfterRender/OnAfterRenderAsync方法有一个bool类型的形参firstRender,用于指示是否是第一次渲染(即组件初始化时的渲染)。

(2)同步方法总是先于异步方法执行。

(3)StateHasChanged 强制实现组件刷新。

2.代码实例

https://github.com/densen2014/Blazor100/tree/master/BlazorLifecycle

mkdir BlazorLifecycle
cd BlazorLifecycle
dotnet new blazorserver

3. Index.razor

代码

@page "/"
@page "/{StartDate:datetime}"
@page "/2/{StartDate2:datetime}"

@using Microsoft.Extensions.Logging
@inject ILogger<Index> Logger

<PageTitle>Index</PageTitle>

<pre>@message</pre>

<button @onclick="LogInformation">Log information (and trigger a render)</button>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnInitialized";
    }

    protected override async Task OnInitializedAsync()
    {
        message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnInitializedAsync";
        await Task.CompletedTask;
    }

    [Parameter]
    public DateTime StartDate { get; set; }

    [Parameter]
    public DateTime StartDate2 { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSet=>没有设置参数:开始日期。应用默认值 (StartDate: {StartDate}).";
        }
        else
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSet=>设置参数:开始日期 (StartDate: {StartDate}).";
        }
    }

    protected override async Task OnParametersSetAsync()
    {
        if (StartDate2 == default)
        {
            StartDate2 = DateTime.Now;

            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSetAsync=>没有设置参数:开始日期。应用默认值 (StartDate2: {StartDate2}).";
        }
        else
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSetAsync=>设置参数:开始日期 (StartDate2: {StartDate2}).";
        }
        await Task.CompletedTask;
    }

    protected override void OnAfterRender(bool firstRender)
    {

        if (firstRender)
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnAfterRender=>为第一次渲染执行.";
            StateHasChanged();
        }
        else
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnAfterRender=>非第一次渲染.";
        }

    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {

        if (firstRender)
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnAfterRenderAsync=>为第一次渲染执行.";
            StateHasChanged();
        }
        else
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnAfterRenderAsync=>非第一次渲染.";
        }

        await Task.CompletedTask;
    }

    private void LogInformation()
    {
        Logger.LogInformation("LogInformation called");
    } 
}

4. 执行结果

点击按钮后

5. 子组件 Index1.razor

@page "/Index2"
@page "/Index2/{StartDate:datetime}"
@page "/Index22/{StartDate2:datetime}"
@page "/Index222/{Count:int}"

@using Microsoft.Extensions.Logging
@inject ILogger<Index> Logger

<h3>子组件</h3>

<pre>@message</pre>

<button @onclick="LogInformation">Log information (and trigger a render)</button>

<p>刷新计数器: @refreshTimes</p>

@if (listItem == null)
{
    <p>Loading... 模拟加载数据3秒</p>
}
else
{
    <p>数据列表</p>
    <ui>
        @foreach (var item in listItem)
        {
            <li>@item</li>
        }
    </ui>
}
@{
    refreshTimes++;
}

@code {
    private string? message;
    private List<string>? listItem;

    [Parameter]
    public DateTime StartDate { get; set; }

    [Parameter]
    public DateTime StartDate2 { get; set; }

    [Parameter]
    public int? Count { get; set; }

    private int refreshTimes;

    protected override void OnInitialized()
    {
        message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnInitialized";
    }

    protected override async Task OnInitializedAsync()
    {
        message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnInitializedAsync";
        await Task.CompletedTask;
    }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSet=>没有设置参数:开始日期。应用默认值 (StartDate: {StartDate}).";
        }
        else
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSet=>设置参数:开始日期 (StartDate: {StartDate}).";
        }
        if (Count == default)
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSet=>没有设置参数:计数.";
        }
        else
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSet=>设置参数:计数 (Count: {Count}).";
        }
    }

    protected override async Task OnParametersSetAsync()
    {
        if (StartDate2 == default)
        {
            StartDate2 = DateTime.Now;

            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSetAsync=>没有设置参数:开始日期。应用默认值 (StartDate2: {StartDate2}).";
        }
        else
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSetAsync=>设置参数:开始日期 (StartDate2: {StartDate2}).";
        }
        if (Count == default)
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSetAsync=>没有设置参数:计数.";
        }
        else
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSetAsync=>设置参数:计数 (Count: {Count}).";
            listItem = null;
            await MockData();
        }
        await Task.CompletedTask;
    }

    protected override void OnAfterRender(bool firstRender)
    {

        if (firstRender)
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnAfterRender=>为第一次渲染执行.";
            StateHasChanged();
        }
        else
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnAfterRender=>非第一次渲染.";
        }

    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {

        if (firstRender)
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnAfterRenderAsync=>为第一次渲染执行.";
            StateHasChanged();

            if (Count == null) {
                Count = Count ?? 5;
                message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnAfterRenderAsync=>没有设置参数:计数,使用默认值 5.";
                await MockData();
            }
        }
        else
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnAfterRenderAsync=>非第一次渲染.";
        }

        await Task.CompletedTask;
    }

    private void LogInformation()
    {
        Logger.LogInformation("LogInformation called");
    }


    protected async Task MockData()
    {
        message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} MockData Delay 3s 模拟加载数据";
        StateHasChanged();

        if (listItem == null)
        {
            await Task.Delay(3000);
            listItem = new List<string>();
            for (int i = 0; i < Count; i++)
            {
                listItem.Add(Guid.NewGuid().ToString());
            }
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} MockData 模拟加载数据完成 , 刷新UI";
            StateHasChanged();
        }

    }
}

6. 改造 Index.razor

在最后一行加入代码

<Index1 StartDate="new DateTime (2019,1,1)" Count="count" />
Count : 
<InputNumber @bind-Value="@count" />
<button @onclick="SetCount">Set</button>

@code {

   private int? count=2;

   private void SetCount()
    {
        Logger.LogInformation("SetCount");
    }
}

7.改造 NavMenu.razor

<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> 生命周期
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="/1999-9-9">
                <span class="oi oi-home" aria-hidden="true"></span> 生命周期带参
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="index2">
                <span class="oi oi-plus" aria-hidden="true"></span> 子组件
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="index222/10">
                <span class="oi oi-plus" aria-hidden="true"></span> 子组件带参
            </NavLink>
        </div> 
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="Index22/2999-9-9">
                <span class="oi oi-plus" aria-hidden="true"></span> 子组件带参2
            </NavLink>
        </div> 
    </nav>
</div>

7. 执行结果

首次运行

设置参数10

其他链接

生命周期带参

子组件

子组件带参

子组件带参2

8. 总结

预呈现后的有状态重新连接

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

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

在最终呈现组件时,这可能导致 UI 中显示的数据发生明显变化。 若要避免在 Blazor Server 应用中出现此双重呈现行为,请传递一个标识符以在预呈现期间缓存状态并在预呈现后检索状态。

检查 listItem 为空显示载入中,数据就绪后再刷新页面显示数据

@if (listItem == null)
{
    <p>Loading... 模拟加载数据3秒</p>
}
else
{
    <p>数据列表</p>
    <ui>
        @foreach (var item in listItem)
        {
            <li>@item</li>
        }
    </ui>
}

设置参数后执行

    protected override async Task OnParametersSetAsync()
    {
        if (Count != default)
        {
            message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnParametersSetAsync=>设置参数:计数 (Count: {Count}).";
            listItem = null;
            await MockData();
        }
        await Task.CompletedTask;
    }

未设置参数异步载入数据

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {

        if (firstRender)
        {
            if (Count == null) {
                Count = Count ?? 5;
                message += $"{Environment.NewLine}{DateTime.Now:hh:mm:ss.fff} OnAfterRenderAsync=>没有设置参数:计数,使用默认值 5.";
                await MockData();
            }
        }

        await Task.CompletedTask;
    }