Share via


ASP.NET Core Blazor 進階案例 (轉譯樹狀結構)

注意

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

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

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

本文會描述手動使用 RenderTreeBuilder 來建置 Blazor 轉譯樹狀的進階案例。

警告

使用 RenderTreeBuilder 來建立元件是個 進階案例。 格式不正確的元件 (例如未封閉的標記標籤) 可能會導致未定義的行為。 未定義的行為包括內容轉譯中斷、應用程式功能遺失,以及 安全性遭入侵

手動建置轉譯樹狀結構 (RenderTreeBuilder)

RenderTreeBuilder 會提供操作元件和元素的方法,包括以 C# 程式碼手動建置元件。

請考慮下列 PetDetails 元件,此元件可以在另一個元件中手動轉譯。

PetDetails.razor

<h2>Pet Details</h2>

<p>@PetDetailsQuote</p>

@code
{
    [Parameter]
    public string? PetDetailsQuote { get; set; }
}

在下列 BuiltContent 元件中,CreateComponent 方法中的迴圈會產生三個 PetDetails元件。

在有序號的 RenderTreeBuilder 方法中,序號是原始程式碼行號。 Blazor 差異演算法依賴對應至不同程式碼的序號,而不是不同的呼叫叫用。 使用 RenderTreeBuilder 方法建立元件時,請將序號的引數硬式編碼。 使用計算或計數器來產生序號可能會導致效能不佳。 如需詳細資訊,請參閱 序號與程式碼號而非執行順序相關聯 的章節。

BuiltContent.razor

@page "/built-content"

<PageTitle>Built Content</PageTitle>

<h1>Built Content Example</h1>

<div>
    @CustomRender
</div>

<button @onclick="RenderComponent">
    Create three Pet Details components
</button>

@code {
    private RenderFragment? CustomRender { get; set; }

    private RenderFragment CreateComponent() => builder =>
    {
        for (var i = 0; i < 3; i++) 
        {
            builder.OpenComponent(0, typeof(PetDetails));
            builder.AddAttribute(1, "PetDetailsQuote", "Someone's best friend!");
            builder.CloseComponent();
        }
    };

    private void RenderComponent()
    {
        CustomRender = CreateComponent();
    }
}
@page "/built-content"

<h1>Build a component</h1>

<div>
    @CustomRender
</div>

<button @onclick="RenderComponent">
    Create three Pet Details components
</button>

@code {
    private RenderFragment? CustomRender { get; set; }

    private RenderFragment CreateComponent() => builder =>
    {
        for (var i = 0; i < 3; i++) 
        {
            builder.OpenComponent(0, typeof(PetDetails));
            builder.AddAttribute(1, "PetDetailsQuote", "Someone's best friend!");
            builder.CloseComponent();
        }
    };

    private void RenderComponent()
    {
        CustomRender = CreateComponent();
    }
}

警告

Microsoft.AspNetCore.Components.RenderTree 中的型別允許處理轉譯作業的 結果。 這些是 Blazor 架構實作的內部詳細資料。 這些型別應該被視為 不穩定,而且在未來版本中可能會變更。

序號與程式碼號而非執行順序相關

一律會編譯 Razor 元件檔案 (.razor)。 執行編譯的程式碼比解譯程式碼具有潛在的優勢,因為產生編譯器代碼的編譯步驟可用來插入資訊,從而提高應用程式在運行時的效能。

這些改進的關鍵範例包括 序號 。 序號會向執行階段指出輸出來自哪些相異和排序的程式碼。 執行階段會使用這項資訊,以線性時間產生有效率的樹狀差異,這比一般樹狀結構差異演算法的速度要快得多。

請考慮下列 Razor 元件檔案 (.razor):

@if (someFlag)
{
    <text>First</text>
}

Second

上述 Razor 標記和文字內容會編譯成類似下方的 C# 程式碼:

if (someFlag)
{
    builder.AddContent(0, "First");
}

builder.AddContent(1, "Second");

當程式碼第一次執行且 someFlagtrue 時,建立器會收到下表中的序列。

序列 類型 資料
0 Text node First
1 Text node Second

假設 someFlag 變成了 false,且標記會再次轉譯。 這次,建立器會收到下表中的序列。

序列 類型 資料
1 Text node Second

當執行階段執行差異時,它會看到順序 0 上的項目已移除,因此它只需一步即可產生以下簡單的編輯指令碼:

  • 移除第一個文字節點。

以程式設計方式產生序號的問題

想像您改為撰寫下列轉譯樹狀結構建立器邏輯:

var seq = 0;

if (someFlag)
{
    builder.AddContent(seq++, "First");
}

builder.AddContent(seq++, "Second");

下表反映第一個輸出。

序列 類型 資料
0 Text node First
1 Text node Second

此結果與先前的案例相同,因此不存在任何負面問題。 someFlag 位於 false 第二個轉譯上,可在下表看到輸出。

序列 類型 資料
0 Text node Second

這次,差異演算法會看到已發生的 兩個 變更。 演算法會產生下列編輯指令碼:

  • 將第一個文字節點的值變更為 Second
  • 移除第二個文字節點。

產生序號已遺失原始程式碼中 if/else 分支和迴圈所在位置的所有實用資訊。 這會導致差異是之前的 兩倍長

這是一個簡單的範例。 在具有複雜且深層巢狀結構的案例,特別是使用迴圈的情況下,效能成本通常會較高。 差異演算法必須深入轉譯樹狀結構,而不是立即識別插入或移除的迴圈區塊或分支。 這通常會導致建置較長的編輯腳本,因為差異演算法對新舊結構彼此的關聯性有誤。

指導和結論

  • 如果動態產生序號,應用程式效能就會受到負面影響。
  • 架構無法在執行階段自動建立自己的序號,因為除非已在編譯時間被擷取,必要的資訊不會存在。
  • 請勿撰寫手動實作 RenderTreeBuilder 邏輯的長區塊。 偏好 .razor 檔案,並允許編譯器處理序號。 如果您無法避免手動 RenderTreeBuilder 邏輯,請將長區塊的程式碼分割成包裝在 OpenRegion/CloseRegion 呼叫中的較小片段。 每個區域都有自己的序號分隔空間,因此您可以從每個區域內的零 (或任何其他任一數字) 重新開機。
  • 如果序號為硬式編碼,差異演算法只需要序號增加值。 初始值和間距無關。 其中一個合法選項是使用程式碼行號做為序號,或從零開始,並以一或數百個遞增 (或任何慣用的間隔)。
  • Blazor 會使用序號,而其他樹狀結構差異 UI 架構則不會使用它們。 使用序號時,差異會快得多,而且 Blazor 具有編譯步驟的優點,可自動處理開發人員撰寫 .razor 檔案的序號。