原文鏈接:https://blazor-university.com/templating-components-with-renderfragements/passing-placeholders-to-renderfragments/
將占位符傳遞給 RenderFragments
源代碼[1]
說明:此頁面的靈感來自用戶 ?ister?agoo 的 Twitter 帖子。
首先,聲明 RenderFragment<RenderFragment>
類型的 [Parameter]
屬性可能看起來不直觀,或者可能有點奇怪。
[Parameter]
public?RenderFragment<RenderFragment>?ChildContent?{?get;?set;?}
事實上,如果您曾經創建過自定義 Blazor 布局,那么您已經熟悉了類似的概念。
RenderFragment<T>
中的 <T>
作為 @context
變量傳遞給用戶指定的標記。布局使用名稱 @Body
而不是 @context
,但 @Body
實際上是一個 RenderFragment
。為了證明這一點,編輯 /Shared/MainLayout.razor
文件并將 @Body
更改為以下內容。
@Body.GetType().Name
現在我們將看到類名,而不是顯示 @Body
渲染的內容,它恰好是 RenderFragment
。
雖然這并不是 Blazor 布局的工作原理,但它是一個有用的比較,有助于理解聲明 RenderFragment<RenderFragment>
類型的 [Parameter]
屬性背后的原理。
<div?class="our-main-layout">@Body
</div>
在前面的虛構布局中,我們可以將整個標記想象為某個父組件的 ChildContent RenderFragment
,而第 3 行的 @Body
相當于 @context(命名為 Body)
,我們可以選擇在我們希望的任何地方注入。在本示例中,我們選擇將 Body
注入 HTML <div>
元素中。。
對應的等價代碼
<OurComponent><p>@context</p>
</OurComponent>
或者,如果我們想使用名稱 Body(或任何其他名稱)
而不是上下文,我們可以指定用于上下文的名稱。請參閱將數據傳遞給 RenderFragment[2] 末尾的避免 @context 名稱沖突部分。
<OurComponent?Context="FragmentWeNeedToRender"><div?class="our-wrapped-fragment">@FragmentWeNeedToRender</div>
</OurComponent>
創建一個工作示例
首先,創建一個我們可以用來綁定一些數據的類。
public?class?Person
{public?string?Salutation?{?get;?set;?}public?string?GivenName?{?get;?set;?}public?string?FamilyName?{?get;?set;?}
}
創建一個簡單的模板化重復組件
此詳細信息類似于使用 @typeparam 創建通用組件[3]一節中介紹的內容。
接下來,我們需要在 /Shared 中創建一個名為 DataList.razor 的新組件。該組件將是一個通用組件(使用 @typeparam
),并將采用一個 IEnumerable<TItem>
并迭代可枚舉以使用其使用者指定的模板呈現每個項目的內容。
@typeparam?TItem
<ul>@foreach?(TItem?item?in?Data????Array.Empty<TItem>()){@ItemTemplate(item)}
</ul>
@code
{[Parameter]public?IEnumerable<TItem>?Data?{?get;?set;?}[Parameter]public?RenderFragment<TItem>?ItemTemplate?{?get;?set;?}
}
這個組件可能會被我們的 Index
頁面使用,如下所示:
@page?"/"<DataList?Data=@Data><ItemTemplate><li?@key=context>@context.Salutation?@context.GivenName?@context.FamilyName</li></ItemTemplate>
</DataList>@code
{private?IEnumerable<Person>?People;protected?override?void?OnInitialized(){base.OnInitialized();People?=?new?Person[]{new?Person?{?Salutation?=?"Mr",?GivenName?=?"Bob",?FamilyName?=?"Geldof"?},new?Person?{?Salutation?=?"Mrs",?GivenName?=?"Angela",?FamilyName?=?"Rippon"?},new?Person?{?Salutation?=?"Mr",?GivenName?=?"Freddie",?FamilyName?=?"Mercury"?}};}
}
注意: <li>
元素中的 @key=context
用于優化性能,應在呈現列表時使用。有關更多信息,請參閱使用 @key 進行優化[4]。
問題
如果我們的 DataList
組件不僅輸出項目列表怎么辦。也許它有一個頁腳,允許用戶使用“上一個”/“下一個”按鈕一次顯示一頁元素,還有一個頁腳顯示總共有多少項目?
我們可以簡單地將這個額外的標記添加到我們的 DataList
組件中,但是如果我們的組件的使用者也想要以不同的方式呈現列表怎么辦?也許他們需要在一個地方將分頁列表顯示為 HTML <table>
,而在其他地方顯示 HTML <ul>
?
<div?class="paged-data-list"><div?class="paged-data-list_header">@Data.Count()?item(s)</div><div?class="paged-data-list_body"><!--?Consumer?wants?either?a?<table>?or?a?<ul>?here?-->@foreach(TItem?item?in?CurrentPageOfData){@ItemTemplate(item)}<!--?Consumer?wants?either?a?</table>?or?a?</ul>?here?--></div><div?class="paged-data-list_footer"><button?type="button"?etc>Prev</button><button?type="button"?etc>Next</button>?</div>
</div>
這正是需要我們讓用戶傳入 RenderFragment<RenderFragment>
的原因。
在 RenderFragment 中渲染 RenderFragment
現在我們的 ItemTemplate
接收了 Person
類型的 @context
以呈現每個元素,我們需要允許我們的組件的使用者指定在第一個元素之前和最后一個元素之后要包含的內容 - 如前面的代碼中突出顯示的那樣第 6 行和第 11 行的示例。
組件使用代碼看起來類似于以下兩個示例中的任何一個
<DataList?Data=@People><ItemTemplate?Context="person"><li?@key=person>@person.Salutation?@person.FamilyName,?@person.GivenName</li></ItemTeplate>
</DataList><DataList?Data=@People><ListTemplate?Context="allPeople"><ul?Type="A">@allPeople</ul></ListTemplate><ItemTemplate?Context="person"><li?@key=person>@person.Salutation?@person.FamilyName,?@person.GivenName</li></ItemTemplate>
</DataList>
第 2 行
定義要用于列表的模板。在這種情況下,我們用
<ul>
和</ul>
包裝我們的內部內容(我們的數據項“allPeople”)。第 4 行
執行通過我們選擇稱為“allPeople”的上下文變量傳遞的
RenderFragment
來指示在何處渲染項目標記。第 7 行
為每個項目指定一個模板。在這種情況下,上下文將是
Person
的一個實例,因此我們選擇通過指定Context="person"
以名稱“person”來引用上下文。
將渲染的內容作為占位符傳遞給消費者顯示
DataList
組件需要呈現 Data
屬性中的每個項目,然后將其傳遞給使用者以決定在其 ListTemplate
中的哪個位置呈現該輸出。或者,更準確地說,DataList
需要傳遞一個 RenderFragment
,它會在執行時呈現項目的標記。
首先,我們將 ListTemplate
屬性添加到我們的 DataList
組件中。
[Parameter]
public?RenderFragment<RenderFragment>?ListTemplate?{?get;?set;?}
接下來,我們將更改我們的 DataList
,以便當消費者沒有指定 ListTemplate
時它使用 <ul>
和 <li>
作為默認值,之后我們將處理組件使用者確實想要使用自定義 ListTemplate
的場景.
使用默認列表模板進行渲染
這是簡單的部分。我們需要做的就是編寫我們的標記,就好像組件上沒有 ListTemplate
這樣的東西,就像我們通常那樣——但前提是 ListTemplate
屬性為空。
@typeparam?TItem
@if?(ListTemplate?==?null)
{<ul>@foreach?(TItem?item?in?Data????Array.Empty<TItem>()){@ItemTemplate(item)}</ul>
}@code
{[Parameter]public?IEnumerable<TItem>?Data?{?get;?set;?}[Parameter]public?RenderFragment<TItem>?ItemTemplate?{?get;?set;?}[Parameter]public?RenderFragment<RenderFragment>?ListTemplate?{?get;?set;?}
}
第 2 行
檢查
ListTemplate
是否為空第 4-9 行
如果
ListTemplate
為 null,那么我們渲染一個標準的<ul>
列表,然后使用ItemTemplate
渲染Data
中的元素。
使用自定義 ListTemplate 進行渲染
RenderFragment<TItem>
期望我們在每次渲染它時傳遞一個 TItem
實例。這很簡單,因為我們有一個 IEnumerable<TItem>
,我們可以從中提取要渲染的值,但是當我們需要將 RenderFragment
的實例傳遞給我們的模板時該怎么辦?
要定義非泛型 RenderFragment
,我們可以使用標準 Razor 轉義序列來表示 HTML,即@
:
RenderFragment?rf?=?@<h1>Hello</h1>;
要定義 RenderFragment<T>
,我們需要使用傳入 T
實例的 lambda 表達式
RenderFragment<Person>?rf?=?person?=>?@<h1>Hello?@person.Name</h1>;
那么我們如何返回一個 RenderFragment
,它只會在渲染時循環遍歷 Data
屬性中的項目?為此,我們需要使用 wig-pig
語法。
wig-pig
wig-pig
是 Razor 渲染引擎可用于表示 C# 文件中的 Razor 標記塊的一組字符。出于顯而易見的原因,此字符序列僅適用于 .razor
文件。如有雷同,純屬巧合。
注意: @:@{
實際上是兩個字符序列。第一個 @:
告訴 Razor 解析器將以下文本視為 Razor 標記,然后 @{
是 C# 代碼塊的開始——它顯然會在某個地方以補充 }
結束。最終,這給了我們一大塊 Razor 標記,它相當于一個帶有 C# 代碼的 RenderFragment
,它可以執行諸如循環之類的操作。
在 Razor 文件中調用 @ListItem(...)
時,我們可以使用 wig-pig
語法將 RenderFragment
作為參數傳遞。
@ListTemplate(@:@{foreach?(TItem?item?in?Data????Array.Empty<TItem>()){@item.ToString()}}
)
使用這種語法,我們渲染組件使用者在 <ListTemplate>
中指定的標記,并傳入一個 RenderFragment
,該 RenderFragment
將渲染 Data
中的所有元素。在前面的代碼中,它只是對 Data
中的每個項目調用 ToString()
。但是,理想情況下,我們希望組件使用者為我們提供的 ItemTemplate
。
@ListTemplate(@:@{foreach?(TItem?item?in?Data????Array.Empty<TItem>()){@ItemTemplate(item)}}
)
DataList.razor
@typeparam?TItem
@if?(ListTemplate?==?null)
{<ul>@foreach?(TItem?item?in?Data????Array.Empty<TItem>()){@ItemTemplate(item)}</ul>
}
else
{@ListTemplate(@:@{foreach?(TItem?item?in?Data????Array.Empty<TItem>()){@ItemTemplate(item)}})
}@code
{[Parameter]public?IEnumerable<TItem>?Data?{?get;?set;?}[Parameter]public?RenderFragment<TItem>?ItemTemplate?{?get;?set;?}[Parameter]public?RenderFragment<RenderFragment>?ListTemplate?{?get;?set;?}
}
使用我們的 DataList
示例 1:一個簡單的列表
<DataList?Data=@People><ItemTemplate><li?@key=context>@context.Salutation?@context.GivenName?@context.FamilyName</li></ItemTemplate>
</DataList>
生成以下 HTML
<ul><li>Mr?Bob?Geldof</li><li>Mrs?Angela?Rippon</li><li>Mr?Freddie?Mercury</li>
</ul>
示例 2:一個 HTML 表格
<DataList?Data=@People><ListTemplate?Context="listOfPeople"><table?border=1?cellpadding=4><thead><tr><th>Salutation</th><th>Given?name</th><th>Family?name</th></tr></thead><tbody>@listOfPeople</tbody></table></ListTemplate><ItemTemplate?Context="person"><tr?@key=@person><td>@person.Salutation</td><td>@person.GivenName</td><td>@person.FamilyName</td></tr></ItemTemplate>
</DataList>
生成以下 HTML
<table?cellpadding="4"?border="1"><thead><tr><th>Salutation</th><th>Given?name</th><th>Family?name</th></tr></thead><tbody><tr><td>Mr</td><td>Bob</td><td>Geldof</td></tr><tr><td>Mrs</td><td>Angela</td><td>Rippon</td></tr><tr><td>Mr</td><td>Freddie</td><td>Mercury</td></tr></tbody>
</table>
未指定 RenderFragments 時分配默認值
目前,我們必須在視圖中使用 @if
語句檢查 ListTemplate
是否為 null,我們甚至沒有檢查 ItemTemplate
是否已設置。這些方法都不是理想的。
相反,如果組件使用者沒有設置我們的 RenderFragment
屬性,我們應該將它們設置為所需的默認值。這樣我們組件的渲染邏輯就可以變得更加簡單。
@ListTemplate(@:@{foreach(TItem?item?in?CurrentPage){@ItemTemplate(item)}}
)
在我們的組件中重寫 OnParametersSet()
并確保 ItemTemplate
屬性不為空。為此,我們創建一個接收 TItem
并返回 RenderFragment
的 lambda(使用 wig-pig
語法)。
protected?override?void?OnParametersSet(){if?(ItemTemplate?==?null){ItemTemplate?=?(item)?=>?@:@{?<li?@key=item>@item.ToString()</li>};}
}
為了確保 ListTemplate
不為空,我們創建了一個接收 RenderFragment
的 lamba,并返回我們自定義的 wig-pig RenderFragment
。
if?(ListTemplate?==?null){ListTemplate?=?_?=>?@:@{?<ul>@foreach(TItem?item?in?CurrentPage){@ItemTemplate(item)}</ul>};}
注意: ;不能與前面的 } 在同一行,否則 Razor 解析器無法正確解析源。這已被報告為一個錯誤,并有望在不久的將來得到修復。
總結
當組件使用者希望在其標記中為將在渲染期間傳遞給他們的內容識別占位符時,應使用 RenderFragment<RenderFragment>
技術 - 例如 Blazor 布局中的 Body
占位符。
希望本節可以幫助您了解何時使用此技術,以及如何使用 wig-pig @:@{
語法來就地生成 RenderFragments
。
補充閱讀
本節的源代碼包括一個使用 `PagedDataList` 組件[5]的附加示例頁面。
參考資料
[1]
源代碼: https://github.com/mrpmorris/blazor-university/tree/master/src/TemplatedComponents/PassingPlaceholdersToRenderFragments
[5]使用 PagedDataList
組件: https://github.com/mrpmorris/blazor-university/blob/master/src/TemplatedComponents/PassingPlaceholdersToRenderFragments/Shared/PagedDataList.razor