ASP.NET Core Blazor 進階案例 (轉譯樹狀結構)
注意
這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援原則。 如需目前版本,請參閱本文的 .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"
<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");
當程式碼第一次執行且 someFlag
為 true
時,建立器會收到下表中的序列。
序列 | 類型 | 資料 |
---|---|---|
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
檔案的序號。