共用方式為


ASP.NET Core Razor 元件生命週期

備註

這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。

警告

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊不承擔任何明示或默示的保證。

如需目前的版本,請參閱 本文的 .NET 9 版本。

本文說明 ASP.NET Core Razor 元件生命週期,以及如何使用生命週期事件。

生命週期事件

Razor 元件會在一組同步和非同步生命週期方法中處理 Razor 元件生命週期事件。 您可以覆寫生命週期方法,以在元件初始化和轉譯期間執行附加作業。

本文簡化了元件生命週期事件處理,以便釐清複雜的架構邏輯,並不涵蓋多年來所做的每項變更。 您可能需要存取 ComponentBase 參考來源 ,以整合自訂事件處理與 Blazor 的生命週期事件處理。 參考來源中的程式碼批註包含生命週期事件處理的其他備註,且這些生命週期事件處理未出現在本文或 API 文件中。

注意

.NET 參考來源的文件連結通常會載入儲存庫的預設分支,這個分支代表下一版 .NET 的當前開發進程。 若要選取特定版本的標籤,請使用 [切換分支或標籤] 下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤

下列簡化圖表說明 Razor 元件生命週期事件處理。 與生命週期事件相關聯的 C# 方法在本文以下各節中提供了範例進行說明。

元件生命週期事件:

  1. 如果元件在收到請求後第一次呈現:
    • 建立元件的執行個體。
    • 執行屬性注入。
    • 呼叫 OnInitialized{Async}。 如果傳回不完整的 Task,則 Task 會等候,然後重新轉譯元件。 同步方法是在非同步方法之前呼叫。
  2. 呼叫 OnParametersSet{Async}。 如果傳回不完整的 Task,則 Task 會等候,然後重新轉譯元件。 同步方法是在非同步方法之前呼叫。
  3. 執行所有同步工作並完成 Task

注意

在生命週期事件中執行的異步動作可能會延遲元件轉譯或顯示數據。 如需詳細資訊,請參閱本文後面一節,標題為 呈現 未完成的非同步動作。

父元件會在其子元件之前渲染,因為渲染是決定哪些子元件存在的因素。 如果使用同步父代元件初始化,則保證父代初始化會先完成。 如果使用非同步父代元件初始化,則無法判斷父代和子系元件初始化的完成順序,因為該順序取決於執行中的初始化程式碼。

Razor 中 Blazor 元件的元件生命週期事件

DOM 事件處理:

  1. 事件處理常式正在執行。
  2. 如果傳回不完整的 Task,則 Task 會等候,然後重新轉譯元件。
  3. 執行所有同步工作並完成 Task

DOM 事件處理

Render 生命週期:

  1. 當下列兩個條件均符合時,避免在元件上進行進一步的轉譯作業:
  2. 建立渲染樹狀結構的差異,並渲染元件。
  3. 等候 DOM 更新。
  4. 呼叫 OnAfterRender{Async}。 同步方法是在非同步方法之前呼叫。

渲染生命週期

開發人員呼叫 StateHasChanged 會導致重新轉譯。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件轉譯

預先呈現期間的靜止狀態

在伺服器端 Blazor 應用程式中,預渲染會等到 靜止狀態,這表示只有當渲染樹中的所有元件都完成渲染後,元件才會被渲染。 當元件在初始化和其他生命週期方法中進行長時間的工作時,因為缺乏活動而導致渲染明顯延遲,這會帶來不佳的用戶體驗。 如需詳細資訊,請參閱本文後面一節,標題為 呈現 未完成的非同步動作。

設定參數時 (SetParametersAsync)

SetParametersAsync 設定由元件的父層在呈現樹狀結構中或由路由參數所提供的參數。

方法的ParameterView參數包含元件在每次呼叫SetParametersAsync時的元件參數值集。 藉由覆寫 SetParametersAsync 方法,開發人員程式碼可以直接與 ParameterView 的參數互動。

的預設實作會使用具有對應值的 屬性,在 中設定每個屬性的值。 在 ParameterView 中沒有對應值的參數會保持不變。

一般而言,覆寫 await base.SetParametersAsync(parameters); 時,您的程式碼應該呼叫基底類別方法 (SetParametersAsync)。 在進階案例中,開發人員程式碼可以透過不叫用基底類別方法所需的任何方式解譯傳入參數的值。 例如,不需要將傳入參數指派給類別的屬性。 不過,在建構程式碼而不呼叫基底類別方法時,您必須參考 ComponentBase 參考來源,因為其會呼叫其他生命週期方法,並以複雜的方式觸發轉譯。

注意

.NET 參考來源的文件連結通常會載入存放庫中的預設分支,此分支代表著 .NET 下一版的目前開發。 若要選取特定版本的標籤,請使用 [切換分支或標籤] 下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤

如果您想要依賴 ComponentBase.SetParametersAsync 的初始化和轉譯邏輯,但不處理傳入參數,您可以選擇將空白 ParameterView 傳遞至基底類別方法:

await base.SetParametersAsync(ParameterView.Empty);

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除連結。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件處置

在下列範例中,如果剖析 ParameterView.TryGetValue 的路由參數成功,則 Param 會將 value 參數的值指派給 Param。 當 value 不是 null 時,值會依元件顯示。

雖然路由參數比對不區分大小寫,但 TryGetValue 只會比對路由範本中的區分大小寫參數名稱。 下列範例需要使用路由範本中的 /{Param?},才能使用 TryGetValue 而非 /{param?} 取得值。 如果在此案例中使用 /{param?},則 TryGetValue 會傳回 falsemessage未設定為任一message字串。

SetParamsAsync.razor

@page "/set-params-async/{Param?}"

<PageTitle>Set Parameters Async</PageTitle>

<h1>Set Parameters Async Example</h1>

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<PageTitle>Set Parameters Async</PageTitle>

<h1>Set Parameters Async Example</h1>

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async"
@page "/set-params-async/{Param}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}

元件初始化 (OnInitialized{Async})

OnInitializedOnInitializedAsync 專門用於在元件執行個體的整個存留期中初始化元件。 參數值和參數值變更不應影響在這些方法中執行的初始化。 例如,將靜態選項載入至下拉式清單中,其在該元件的存留期中不會改變,且不相依於參數值,會在下列其中一個生命週期方法中執行。 如果參數值或參數值變更會影響元件狀態,請改用 OnParametersSet{Async}

當元件在 SetParametersAsync 中收到其初始參數之後,就會叫用這些方法。 同步方法是在非同步方法之前呼叫。

如果使用同步父代元件初始化,則保證父代初始化會在子元件初始化之前完成。 如果使用非同步父代元件初始化,則無法判斷父代和子系元件初始化的完成順序,因為該順序取決於執行中的初始化程式碼。

若為同步作業,請覆寫 OnInitialized

OnInit.razor

@page "/on-init"

<PageTitle>On Initialized</PageTitle>

<h1>On Initialized Example</h1>

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized() => 
        message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"

<PageTitle>On Initialized</PageTitle>

<h1>On Initialized Example</h1>

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized() => 
        message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}

若要執行非同步作業,請覆寫 OnInitializedAsync 並使用 await 運算子:

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

如果自訂基底類別搭配自訂初始化邏輯使用,請在基底類別上呼叫 OnInitializedAsync

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

    await base.OnInitializedAsync();
}

除非自訂基底類別搭配自訂邏輯使用,否則不需要呼叫 ComponentBase.OnInitializedAsync。 如需詳細資訊,請參閱基底類別生命週期方法一節。

元件必須確保當 OnInitializedAsync 從等候可能不完整 Task傳回時,它處於有效的轉譯狀態。 如果方法傳回不完整 Task,則同步完成之方法的一部分必須讓元件處於有效的轉譯狀態。 如需詳細資訊,請參閱 ASP.NET Core Blazor 同步處理內容ASP.NET Core Razor 元件處置的簡介備註。

在伺服器上預先轉譯其內容的 Blazor 應用程式會呼叫 OnInitializedAsync兩次

  • 第一次是在元件開始靜態轉譯為頁面的一部分時。
  • 第二次是在瀏覽器渲染元件時。

若要防止 OnInitializedAsync 中的開發人員程式碼在預先轉譯時執行兩次,請參閱預先轉譯之後的具狀態重新連線一節。 本節內容著重於 Blazor Web App和具狀態的SignalR重新連線。 若要在預先轉譯初始化程式碼執行期間保留狀態,請參閱預先轉譯 ASP.NET CoreRazor 元件

若要防止 OnInitializedAsync 中的開發人員程式碼在預先轉譯時執行兩次,請參閱預先轉譯之後的具狀態重新連線一節。 雖然本節內容著重於 Blazor Server 和具狀態的SignalR重新連線,但在托管 Blazor WebAssembly 方案(WebAssemblyPrerendered) 中的預渲染場景涉及類似的條件和方法,以防止開發人員程式碼被執行兩次。 若要在預先呈現期間保留初始化程式代碼的狀態,請參閱 將 ASP.NET Core Razor 元件整合到 MVC 或 Razor 頁面中

雖然 Blazor 應用程式正在預先渲染,但無法進行如呼叫 JavaScript (JS 互操作) 等特定動作。 預先轉譯時,元件可能需要以不同的方式轉譯。 如需詳細資訊,請參閱使用 JavaScript Interop 預先轉譯一節。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除連結。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件處置

使用串流轉譯搭配靜態伺服器端轉譯 (靜態 SSR) 或預先轉譯,以改善在 OnInitializedAsync 中執行長時間執行非同步工作之元件的使用者體驗,以便完全轉譯。 如需詳細資訊,請參閱以下資源:

設定參數之後 (OnParametersSet{Async})

稱為 OnParametersSetOnParametersSetAsync

  • OnInitializedOnInitializedAsync 中初始化元件之後。

  • 當父元件重新渲染並提供時:

    • 至少有一個參數已改變時的已知或原始不可變類型。
    • 複雜型別參數。 架構無法得知複雜型別參數的值是否已在內部變動,因此當一或多個複雜型別參數存在時,架構一律會將參數集視為已變更。

    如需關於轉譯慣例的詳細資訊,請參閱 ASP.NET Core Razor 元件轉譯

同步方法是在非同步方法之前呼叫。

即使參數值尚未變更,也可以叫用方法。 這種行為強調開發人員需要在方法內實作其他邏輯,以檢查參數值是否確實在重新初始化資料或相依於這些參數的狀態之前已變更。

針對下列範例元件,導航至在 URL 中的元件的頁面:

  • 具有在 StartDate 前收到的開始日期:/on-parameters-set/2021-03-19
  • 沒有開始日期,此情況下 StartDate 會指派目前當地時間的值:/on-parameters-set

注意

在元件路由中,不可能同時使用DateTime參數的路由限制datetime,並將參數設為選擇性。 因此,下列 OnParamsSet 元件會使用兩個 @page 指示詞,來處理 URL 中提供與未提供日期區段的路由。

OnParamsSet.razor

@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<PageTitle>On Parameters Set</PageTitle>

<h1>On Parameters Set Example</h1>

<p>
    Pass a datetime in the URI of the browser's address bar. 
    For example, add <code>/1-1-2024</code> to the address.
</p>

<p>@message</p>

@code {
    private string? message;

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

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

            message = $"No start date in URL. Default value applied " +
                $"(StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used " +
                $"(StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<PageTitle>On Parameters Set</PageTitle>

<h1>On Parameters Set Example</h1>

<p>
    Pass a datetime in the URI of the browser's address bar. 
    For example, add <code>/1-1-2024</code> to the address.
</p>

<p>@message</p>

@code {
    private string? message;

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

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

            message = $"No start date in URL. Default value applied " +
                $"(StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used " +
                $"(StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string? message;

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

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

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string? message;

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

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

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string message;

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

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

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string message;

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

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

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}

套用參數和屬性值時,必須在 OnParametersSetAsync 生命週期事件期間進行非同步工作:

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

如果自訂基底類別搭配自訂初始化邏輯使用,請在基底類別上呼叫 OnParametersSetAsync

protected override async Task OnParametersSetAsync()
{
    await ...

    await base.OnParametersSetAsync();
}

除非自訂基底類別搭配自訂邏輯使用,否則不需要呼叫 ComponentBase.OnParametersSetAsync。 如需詳細資訊,請參閱基底類別生命週期方法一節。

如果在開發人員程式碼中提供了事件處理常式,請在清理時解除它們的連結。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件處置

如果可處置元件未使用 CancellationToken,那麼 OnParametersSetOnParametersSetAsync 應檢查該元件是否已被處置。 如果 OnParametersSetAsync 傳回不完整 Task,元件必須確定同步完成的方法部分會讓元件處於有效的轉譯狀態。 如需詳細資訊,請參閱 ASP.NET Core Blazor 同步處理內容ASP.NET Core Razor 元件處置的簡介備註。

如需路由參數和限制式的詳細資訊,請參閱 ASP.NET Core Blazor 路由和導覽

如需手動實作 SetParametersAsync 以改善某些案例效能的範例,請參閱 ASP.NET Core Blazor 效能最佳做法

元件轉譯之後 (OnAfterRender{Async})

OnAfterRenderOnAfterRenderAsync 會在元件以互動方式轉譯且 UI 完成更新之後叫用 (例如,將元素新增至瀏覽器 DOM 之後)。 元素和元件的參考在此時被填入。 使用此階段來執行轉譯內容的其他初始化步驟,例如與轉譯 DOM 元素互動的 JS Interop 呼叫。 同步方法是在非同步方法之前呼叫。

這些方法不會在伺服器上於預先轉譯或靜態伺服器端轉譯 (靜態 SSR) 期間叫用,因為這些流程不會附加至即時瀏覽器 DOM,且已在更新 DOM 之前完成。

針對 OnAfterRenderAsync,元件不會在任何傳回的 Task 完成之後自動重新轉譯,以避免無限轉譯迴圈。

OnAfterRenderOnAfterRenderAsync 會在元件完成轉譯之後呼叫。 此時會填入元素和元件參考。 使用此階段來執行轉譯內容的其他初始化步驟,例如與轉譯 DOM 元素互動的 JS Interop 呼叫。 同步方法是在非同步方法之前呼叫。

這些方法不會在預先定義期間叫用,因為預先轉譯不會附加至即時瀏覽器 DOM,而且已在更新 DOM 之前完成。

針對 OnAfterRenderAsync,元件不會在任何傳回的 Task 完成之後自動重新轉譯,以避免無限轉譯迴圈。

firstRenderOnAfterRenderOnAfterRenderAsync 參數:

  • 第一次轉譯元件執行個體時會設定為 true
  • 可以用來確保初始化工作只會執行一次。

AfterRender.razor

@page "/after-render"
@inject ILogger<AfterRender> Logger 

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

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

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender) =>
        Logger.LogInformation("firstRender = {FirstRender}", firstRender);

    private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger 

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

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

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender) =>
        Logger.LogInformation("firstRender = {FirstRender}", firstRender);

    private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

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

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@inject ILogger<AfterRender> Logger 

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

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

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger 

<h1>After Render Example</h1>

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

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger

<h1>After Render Example</h1>

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

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

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

載入頁面並選取按鈕時,AfterRender.razor 範例會產生下列輸出至主控台:

OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False

在轉譯之後立即進行的非同步工作,必須在 OnAfterRenderAsync 生命週期事件期間發生:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...
}

如果自訂基底類別搭配自訂初始化邏輯使用,請在基底類別上呼叫 OnAfterRenderAsync

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...

    await base.OnAfterRenderAsync(firstRender);
}

除非自訂基底類別搭配自訂邏輯使用,否則不需要呼叫 ComponentBase.OnAfterRenderAsync。 如需詳細資訊,請參閱基底類別生命週期方法一節。

即使您從 Task 返回 OnAfterRenderAsync,架構在完成該任務後也不會為元件安排進一步的渲染周期。 這是為了避免無限轉譯迴圈。 這與其他生命週期方法不同,其他生命週期方法會在傳回的 Task 完成之後排程進一步的渲染週期。

OnAfterRenderOnAfterRenderAsync在伺服器預先轉譯流程期間不會呼叫。 當元件在預先呈現之後以互動方式呈現時,會呼叫這些方法。 當應用程式預渲染時:

  1. 元件會在伺服器上執行,以在 HTTP 回應中產生一些靜態 HTML 標記。 在此階段期間,不會呼叫 OnAfterRenderOnAfterRenderAsync
  2. 當 Blazor 指令碼 (blazor.{server|webassembly|web}.js) 在瀏覽器中啟動時,元件會以互動式轉譯模式重新啟動。 重新啟動元件之後,「會」呼叫 OnAfterRenderOnAfterRenderAsync,因為應用程式不再處於預先轉譯階段。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件處置

基底類別生命週期方法

覆寫 Blazor 的生命週期方法時,不需要呼叫 ComponentBase 的基底類別生命週期方法。 不過,在下列情況下,元件應該呼叫已覆寫的基底類別生命周期方法:

  • 覆寫 ComponentBase.SetParametersAsync 時,通常會叫用 await base.SetParametersAsync(parameters);,因為基底類別方法會呼叫其他生命週期方法,並以複雜的方式觸發轉譯。 如需詳細資訊,請參閱何時設定參數 (SetParametersAsync) 一節。
  • 如果基底類別方法包含必須執行的邏輯。 程式庫取用者通常會在繼承基底類別時呼叫基底類別生命週期方法,因為程式庫基底類別通常有要執行的自訂生命週期邏輯。 如果應用程式使用程式庫中的基底類別,請參閱程式庫的文件以取得指引。

在下列範例中,會呼叫 base.OnInitialized();,以確保執行基底類別的 OnInitialized 方法。 若沒有呼叫,BlazorRocksBase2.OnInitialized 就不會執行。

BlazorRocks2.razor

@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}

BlazorRocksBase2.cs

using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";

    protected override void OnInitialized() =>
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";

    protected override void OnInitialized() =>
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}

狀態變更 (StateHasChanged)

StateHasChanged 通知元件其狀態已變更。 適用時,調用 StateHasChanged 可在應用程式的主要執行緒空閒時將重新渲染加入佇列。

系統會自動針對 StateHasChanged 方法呼叫 EventCallback。 如需事件回撥的詳細資訊,請參閱 ASP.NET CoreBlazor 事件處理

如需關於元件轉譯和何時呼叫 StateHasChanged (包括何時使用 ComponentBase.InvokeAsync 叫用) 的詳細資訊,請參閱 ASP.NET Core Razor 元件轉譯

處理渲染時不完整的異步操作

在生命週期事件中執行的非同步動作,在轉譯元件前可能尚未完成。 當生命週期方法執行時,物件可能會 null 或不完整地填入資料。 提供轉譯邏輯,以確認物件已初始化。 顯示預留位置的 UI 元素(例如載入訊息),當物件為null時。

在下列 Slow 元件中,OnInitializedAsync 被覆蓋以非同步方式執行長時間運行的工作。 當 isLoadingtrue時,載入訊息會顯示給使用者。 Task 傳回的 OnInitializedAsync 完成之後,元件會以更新的狀態重新呈現,並顯示 「Finished!」 訊息。

Slow.razor

@page "/slow"

<h2>Slow Component</h2>

@if (isLoading)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>Finished!</div>
}

@code {
    private bool isLoading = true;

    protected override async Task OnInitializedAsync()
    {
        await LoadDataAsync();
        isLoading = false;
    }

    private Task LoadDataAsync()
    {
        return Task.Delay(10000);
    }
}

上述元件會使用 isLoading 變數來顯示載入訊息。 類似的方法用於將數據載入集合的元件,並檢查集合是否 null 來呈現載入訊息。 下列範例會檢查 movies 集合中的 null,以決定顯示載入訊息或顯示電影集合:

@if (movies == null)
{
    <p><em>Loading...</em></p>
}
else
{
    @* display movies *@
}

@code {
    private Movies[]? movies;

    protected override async Task OnInitializedAsync()
    {
        movies = await GetMovies();
    }
}

預先呈現會等候 靜止,這表示在渲染樹狀結構中的所有元件都完成渲染之前,該元件不會渲染。 這表示載入訊息不會在子元件的 OnInitializedAsync 方法在預先呈現期間執行長時間執行的工作時顯示。 若要示範此行為,請將上述 Slow 元件放入測試應用程式的 Home 元件:

@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SlowComponent />

注意

雖然本節中的範例討論 OnInitializedAsync 生命週期方法,但在預先呈現期間執行的其他生命週期方法可能會延遲元件的最終轉譯。 在預先呈現期間,OnAfterRender{Async} 不會執行,並且不會因靜止狀態而受到延遲影響。

在預先呈現期間,Home 元件要等到 Slow 元件完成轉譯後才會呈現,而轉譯 Slow 元件需要十秒鐘。 UI 在這個十秒期間是空白的,而且沒有載入訊息。 預渲染之後,Home 元件會渲染,並顯示 Slow 元件的載入訊息。 超過十秒之後,Slow 元件最後會顯示已完成的訊息。

如上述示範所示,在預先呈現期間靜止會導致用戶體驗不佳。 若要改善用戶體驗,請從實施 串流轉譯 開始,以避免在預渲染的時候等待異步工作完成。

[StreamRendering] 屬性 新增至 Slow 元件(在 .NET 8 中使用 [StreamRendering(true)]):

@attribute [StreamRendering]

Home 元件預先呈現時,Slow 元件會快速渲染並顯示載入訊息。 Home 元件不會等候 10 秒,讓 Slow 元件完成轉譯。 不過,當預先呈現結束時,顯示的完成訊息會被載入訊息取代,然後元件開始最終渲染,這又造成了十秒鐘的延遲。 這是因為 Slow 元件會渲染兩次,LoadDataAsync 也會執行兩次。 當元件存取服務與資料庫等資源時,服務呼叫和資料庫查詢的雙重執行會在應用程式的資源上建立不想要的負載。

若要解決載入訊息被重複呈現以及服務和資料庫呼叫的重複執行問題,請將預先呈現的狀態保存到 PersistentComponentState,以供元件的最終渲染,如下列 Slow 元件的更新所示:

@page "/slow"
@attribute [StreamRendering]
@implements IDisposable
@inject PersistentComponentState ApplicationState

<h2>Slow Component</h2>

@if (data is null)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>@data</div>
}

@code {
    private string? data;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        persistingSubscription =
            ApplicationState.RegisterOnPersisting(PersistData);

        if (!ApplicationState.TryTakeFromJson<string>("data", out var restored))
        {
            data = await LoadDataAsync();
        }
        else
        {
            data = restored!;
        }
    }

    private Task PersistData()
    {
        ApplicationState.PersistAsJson("data", data);

        return Task.CompletedTask;
    }

    private async Task<string> LoadDataAsync()
    {
        await Task.Delay(10000);
        return "Finished!";
    }

    void IDisposable.Dispose()
    {
        persistingSubscription.Dispose();
    }
}

透過結合串流渲染與持續性元件狀態:

  • 服務和資料庫只需要單一呼叫元件初始化。
  • 元件會在長時間執行的工作期間快速渲染其UI,並顯示載入訊息,以獲得最佳用戶體驗。

如需詳細資訊,請參閱以下資源:

預先呈現期間的靜止會導致用戶體驗不佳。 延遲問題可以在以 .NET 8 或更新版本為目標的應用程式中透過名為 串流渲染的功能解決,通常會結合 在預渲染期間保存元件狀態 以避免等待異步任務完成。 在 .NET 的版本早於 8.0 時,執行 長時間運行的背景任務,以在最終渲染後載入數據,可能會因系統閒置而解決渲染的長時間延遲問題。

處理錯誤

如需關於在生命週期方法執行期間處理錯誤的資訊,請參閱處理 ASP.NET Core Blazor 應用程式中的錯誤

預渲染後的狀態化重新連線

在伺服器上預先轉譯時,元件一開始會以靜態方式轉譯為頁面的一部分。 一旦瀏覽器與伺服器建立了SignalR連線,元件就會再次被渲染並提供互動功能。 如果元件初始化的 OnInitialized{Async} 生命週期方法存在,則會執行方法兩次

  • 當元件以靜態方式預先轉譯時。
  • 建立伺服器連線之後。

這可能會導致最終轉譯元件時,UI 中顯示的資料有明顯變更。 若要避免此行為,請在預渲染期間傳入識別碼以快取狀態,並在預渲染之後擷取狀態。

下列程式碼示範了 WeatherForecastService,以避免因預先渲染而改變資料顯示。 等候的 Delay (await Task.Delay(...)) 會模擬短暫的延遲,再從 GetForecastAsync 方法傳回資料。

在應用程式的 IMemoryCache 檔案中,在服務集合上新增具有 AddMemoryCacheProgram 服務:

builder.Services.AddMemoryCache();

WeatherForecastService.cs

using Microsoft.Extensions.Caching.Memory;

namespace BlazorSample;

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

    public IMemoryCache MemoryCache { get; } = memoryCache;

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

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

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using Microsoft.Extensions.Caching.Memory;

namespace BlazorSample;

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

    public IMemoryCache MemoryCache { get; } = memoryCache;

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

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

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using Microsoft.Extensions.Caching.Memory;

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)
            });

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

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using Microsoft.Extensions.Caching.Memory;

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)
            });

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

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;

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();
        });
    }
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;

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 BlazorSignalR 指導

本節內容著重於 Blazor Web App和具狀態的 SignalR重新連線。 若要在預先轉譯初始化程式碼執行期間保留狀態,請參閱預先轉譯 ASP.NET CoreRazor 元件

雖然本節內容著重於 Blazor Server 和具狀態的SignalR重新連線,但在託管 Blazor WebAssembly 解決方案 (WebAssemblyPrerendered) 中進行預先轉譯的情景涉及相似的條件和方法,以防止開發人員程式碼被執行兩次。 若要在預呈現時初始化程式碼執行期間保留狀態,請參閱 ASP.NET Core Razor 元件與 MVC 或 Razor 頁面的整合

使用 JavaScript Interop 預渲染

本節適用於會預先轉譯 Razor 元件的伺服器端應用程式。 預先轉譯 ASP.NET Core Razor 元件中會有預先轉譯的說明。

注意

互動式路由中進行內部導覽時,並不涉及向伺服器請求新頁面內容。 因此,內部頁面請求不會進行預渲染。 如果應用程式採用互動式路由,請針對示範預先轉譯行為的元件範例執行完整頁面重載。 如需詳細資訊,請參閱預先轉譯 ASP.NET Core Razor 元件

本節適用於會預先轉譯 Blazor WebAssembly 元件的伺服器端應用程式和所裝載的 Razor 應用程式。 預渲染涵蓋在 整合 ASP.NET Core Razor 元件與 MVC 或 Razor Pages 中。

在預渲染期間,無法調用 JavaScript(JS)。 下列範例示範如何透過與預先轉譯相容的方式,將 JS Interop 作為元件初始化邏輯的一部分。

下列 scrollElementIntoView 函式:

window.scrollElementIntoView = (element) => {
  element.scrollIntoView();
  return element.getBoundingClientRect().top;
}

IJSRuntime.InvokeAsync 在元件程式碼呼叫 JS 函式時, ElementReference 僅在 OnAfterRenderAsync 使用,而不在任何先前的生命週期方法中使用,因為要等到元件轉譯後才會有 HTML DOM 元素。

呼叫 StateHasChanged (參考來源) ,將使用從 JS interop 呼叫取得的新狀態將元件的重新渲染排入佇列 (如需詳細資訊,請參閱 ASP.NET Core Razor 元件渲染)。 此程式碼不會無限迴圈,因為只有在 StateHasChangedscrollPosition時才會呼叫 null

PrerenderedInterop.razor

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

<PageTitle>Prerendered Interop</PageTitle>

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}

上述範例會使用全域函式敗壞用戶端。 如需更適合生產應用程式的方法,請參閱 JavaScript 模組中的 JavaScript 隔離

可取消的背景工作

元件通常會執行長時間執行的背景工作,例如進行網路呼叫 (HttpClient) 並與資料庫互動。 建議您在數種情況下停止背景工作,以節省系統資源。 例如,當使用者瀏覽離開元件時,背景非同步作業不會自動停止。

背景工作項目可能需要取消的其他原因包括:

  • 執行中的背景工作是以錯誤輸入資料或處理參數啟動。
  • 目前執行背景工作項目的集合必須取代為一組新工作項目。
  • 目前執行中工作的優先順序必須變更。
  • 應用程式必須關閉,才能重新部署伺服器。
  • 伺服器資源變得有限,需要重新排程背景工作項目。

若要在元件中實作可取消的背景工作模式:

在以下範例中:

  • await Task.Delay(10000, cts.Token); 代表長時間執行的非同步背景工作。
  • BackgroundResourceMethod 代表長時間執行的背景方法,如果在呼叫方法之前處置 Resource,則不應該啟動此方法。

BackgroundWork.razor

@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<PageTitle>Background Work</PageTitle>

<h1>Background Work Example</h1>

<p>
    <button @onclick="LongRunningWork">Trigger long running work</button>
    <button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
    If you trigger disposal within 10 seconds of page load, the 
    <code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
    If disposal occurs after <code>BackgroundResourceMethod</code> is called but
    before action is taken on the resource, an <code>ObjectDisposedException</code>
    is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
    processed.
</p>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();
    private IList<string> messages = [];

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(10000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");

        if (!cts.IsCancellationRequested)
        {
            cts.Cancel();
        }
        
        cts?.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose() => disposed = true;
    }
}
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<PageTitle>Background Work</PageTitle>

<h1>Background Work Example</h1>

<p>
    <button @onclick="LongRunningWork">Trigger long running work</button>
    <button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
    If you trigger disposal within 10 seconds of page load, the 
    <code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
    If disposal occurs after <code>BackgroundResourceMethod</code> is called but
    before action is taken on the resource, an <code>ObjectDisposedException</code>
    is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
    processed.
</p>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();
    private IList<string> messages = [];

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(10000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");

        if (!cts.IsCancellationRequested)
        {
            cts.Cancel();
        }
        
        cts?.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose() => disposed = true;
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new Resource();
    private CancellationTokenSource cts = new CancellationTokenSource();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}

Blazor Server 重新連線事件

本文所涵蓋的元件生命週期事件會與伺服器端重新連線事件處理常式分開運作。 當與用戶端的 SignalR 連線遺失時,僅會中斷 UI 更新作業。 重新建立連線時,會繼續 UI 更新。 如需線路處理常式事件和設定的詳細資訊,請參閱 ASP.NET Core BlazorSignalR 指導

其他資源

處理在 Razor 元件生命週期外攔截到的例外狀況