Blazor 服务端组件 Render, RenderFragment ,RenderTreeBuilder, CascadingValue/CascadingParameter
一、組件
支撐Blazor的是微軟的兩大成熟技術(shù),Razor模板和SignalR,兩者的交匯點(diǎn)就是組件。通常,我們從ComponentBase派生的類型,或者創(chuàng)建的.razor 文件,就可以稱作組件。基于這兩大技術(shù),組件也就具備了兩大功能,1、生成html片段;2、維護(hù)組件狀態(tài)。這里我們來(lái)說(shuō)一下組件最基本的功能,生成html片段。
二、RenderTreeBuilder,RenderFragment
我們知道,瀏覽器處理HTML 文檔時(shí)會(huì)將所有的標(biāo)簽都掛到一顆文檔樹中,無(wú)論一段HTML來(lái)自哪里,總會(huì)被這棵樹安排的明明白白。換句話說(shuō),如果有根線的話,我們可以依靠這棵樹把所有的標(biāo)簽都串起來(lái),而在Blazor組件中也有這么一根線,這根線就是RenderTreeBuilder,拿這根線的人就是Blazor框架。
備注一下:以下涉及的代碼如果沒(méi)有特別說(shuō)明,都是指寫在.cs文件中,繼承 Microsoft.AspNetCore.Components.ComponentBase 的組件類。
下面用代碼看看這根線。 新建一個(gè)Blazor 應(yīng)用 項(xiàng)目,新增 一個(gè)c#類,MyComp 繼承 Microsoft.AspNetCore.Components.ComponentBase,然后override 一下,找到如下方法:
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);//加斷點(diǎn)
}
加個(gè)斷點(diǎn),在項(xiàng)目的 PagesIndex.razor 里加上一行。<MyComp />
如果不想代碼執(zhí)行兩次,就在Pages_Host.cshtml 里修改一下rendermode
@(await Html.RenderComponentAsync<App>(RenderMode.Server))
F5跑起來(lái),雖然沒(méi)有任何輸出,但是斷點(diǎn)命中了,RenderTreeBuilder這根線確實(shí)串起了我們的組件。
現(xiàn)在讓我們看看,RenderTreeBuilder 可以做什么。
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddMarkupContent(0, "<span> BuildRenderTree 使用 AddMarkupContent 輸出 Html 。</span>");
// base.BuildRenderTree(builder);
}
再次跑起來(lái),我們發(fā)現(xiàn)頁(yè)面上多了我們加的span.也就是說(shuō)HTML的輸出,靠的是調(diào)用RenderTreeBuilder上的各種方法加上的。組件的基本原理也就是這樣,一個(gè)RenderTreeBuilder 進(jìn)入不同組件的 BuildRenderTree 方法,方法內(nèi) 通過(guò)RenderTreeBuilder上的add.. open.. 方法把我們想要輸出的部分,掛載到builder上,最終輸出到瀏覽器。
接下來(lái),我們考察一下BuildRenderTree方法, 用委托描述一下,我們發(fā)現(xiàn)這就是一個(gè)Action<RenderTreeBuilder>.
在標(biāo)題里我們提到了RenderFragment, 查看一下它的定義。
public delegate void RenderFragment(RenderTreeBuilder builder);//還是一個(gè) Action<RenderTreeBuilder>,或者說(shuō),BuildRenderTree 就是一個(gè)RenderFragment
我們發(fā)現(xiàn)和前面的BuildRenderTree 在簽名上一模一樣,既然blazor會(huì)使用RenderTreeBuilder 去調(diào)用BuildRenderTree 方法,那么RenderFragment會(huì)不會(huì)也被調(diào)用?
讓我們暫時(shí)離開組件MyComp,轉(zhuǎn)到Index.razor 內(nèi)加一段code
@code{
RenderFragment MyRender=(builder) => builder.AddMarkupContent(0, "<span>當(dāng)前輸出來(lái)自:Index.razor 組件, MyRender 字段。 </span>");
}
在之前我們聲明 MyComp組件之后,再加一行調(diào)用 @MyRender.
完整的Index.razor
@page "/"
<MyComp />
@MyRender
@code{
RenderFragment MyRender = (builder) => builder.AddMarkupContent(0, "<div>當(dāng)前輸出來(lái)自:Index.razor 組件, MyRender 字段。 </div>");
}
兩段信息,如愿輸出,證明blazor能夠識(shí)別出模板中的 RenderFragment ,并自動(dòng)調(diào)用。
既然我們?cè)诮M件模板中(Index.razor)書寫RenderFragment ,當(dāng)然有其他方式可以不用拼湊字符串。
RenderFragment AnotherRender =@<div>模板寫法的RenderFragment</div>;
加上調(diào)用 @AnotherRender,跑起來(lái),三段信息。
至此,我們對(duì)RenderFragment 有了一個(gè)大概的了解,它是一個(gè)函數(shù),內(nèi)部打包了我們的輸出內(nèi)容。在模板中我們可以使用,@xxxrender將其就地展開輸出,在c#環(huán)境下我們可以通過(guò) xxxrender(builder)的形式進(jìn)行調(diào)用(比如在BuildRenderTree方法內(nèi)調(diào)用)。又因?yàn)槠浔旧砭褪且粋€(gè)委托函數(shù),因此我們即可以在組件內(nèi)使用,也可以自由的在組件之間傳遞, 完成對(duì)輸出內(nèi)容及邏輯的復(fù)用。
同時(shí),為了更好的配合RenderFragment 使用,Blazor中還提供了一個(gè)工廠委托,RenderFragment , 即 Func<TValue,RenderFragment> 用法一般如下
//模板中(Index.razor)
RenderFragment<object> RenderValue =value=> @<div> render value :@value</div>;
調(diào)用 @RenderValue (123) 如果在c#代碼中,比如在BuildRenderTree 方法內(nèi), RenderValue (123)(builder)。
vs中*.razor在編譯時(shí)會(huì)生成對(duì)應(yīng)的.g.cs代碼,位置在obj/debug/netcoreapp3.0/ razor 下,可以多打開看看。
三、RenderFragment 的一些用法
1、html中,我們可以在一對(duì)標(biāo)簽內(nèi)添加 內(nèi)容,比如 <div>123</div>,組件默認(rèn)是不支持此類操作的,這時(shí)我們就需要RenderFragment來(lái)包裝標(biāo)簽內(nèi)的內(nèi)容。
讓我們回到MyComp組件類中,增加一個(gè)屬性
[Parameter] public RenderFragment ChildContent{ get; set; }
Index.razor
<MyComp><div> 組件標(biāo)記內(nèi)部</div></MyComp>
此時(shí)直接運(yùn)行的話,組件不會(huì)輸出內(nèi)部信息,需要在BuildRenderTree 中執(zhí)行一下
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
ChildContent?.Invoke(builder);
base.BuildRenderTree(builder);
}
組件標(biāo)記內(nèi)的片段被打包進(jìn)了 ChildContent,已經(jīng)變成了獨(dú)立的一個(gè)片段,因此需要我們顯式的調(diào)用一下。
ChildContent 是特殊名稱
2、組件上有多個(gè)RenderFragment
[Parameter] public RenderFragment Fragment1 { get; set; }
[Parameter] public RenderFragment Fragment2 { get; set; }
此時(shí)調(diào)用需要調(diào)整一下,不然框架不知道把內(nèi)容片段打包進(jìn)哪個(gè)屬性里
<MyComp>
<Fragment1>
<div> Fragment1 </div>
</Fragment1>
<Fragment1>
<div> Fragment1.1 </div>
</Fragment1>
<Fragment2>
<div> Fragment2 </div>
</Fragment2>
</MyComp>
這里故意重復(fù)處理了Fragment1,可以看看結(jié)果。
3、帶參數(shù)的RenderFragment
code:
[Parameter] public RenderFragment<MyComp> ChildContent { get; set; }
調(diào)用及傳參
<MyComp Context="self" > //<ChildContent>
@self.GetType()
</MyComp> //</ChildContent>
4、打開的組件聲明標(biāo)記內(nèi)部,除了可以使用RenderFragment 參數(shù)屬性外,其他的razor 語(yǔ)法基本都支持,也包括另外一個(gè)組件。
比如
<MyComp>
<CompA />
<CompB> ...... </CompB>
</MyComp>
或者
<MyComp>
<Fragment1>
<CompA />
</Fragment1>
<Fragment2>
<CompB> ...... </CompB>
</Fragment2>
</MyComp>
雖然看上去,聲明標(biāo)記的代碼很相似,但卻有著實(shí)質(zhì)上的不同。
當(dāng)我們使用 標(biāo)記聲明一個(gè)參數(shù)屬性時(shí),我們是在生成RenderFragment,隨后將其賦值給對(duì)應(yīng)的屬性。
當(dāng)我們使用標(biāo)記聲明一個(gè)組件時(shí),我們是在構(gòu)造一個(gè)組件實(shí)例,然后調(diào)用它,將組件輸出插入到組件所在位置。
參數(shù)屬性(RenderFragment )屬于組件,是組件的一個(gè)屬性,互相關(guān)系是明確的類型《=》成員關(guān)系。
組件內(nèi)部的其他組件標(biāo)記很多時(shí)候只是為了復(fù)用一些輸出片段,如果不通過(guò)代碼進(jìn)行一些處理的話,是無(wú)法明確知道組件之間關(guān)系的。
四、CascadingValue/CascadingParameter
組件多起來(lái)之后,組件之間的數(shù)據(jù)共享和傳遞以及組件間的關(guān)系就會(huì)變的很麻煩,數(shù)量少的時(shí)候,還可以使用@ref 手工指定,多起來(lái)之后@ref明顯不是一個(gè)好方法。 組件CascadingValue和對(duì)應(yīng)的特性[CascadingParameter]就是為了解決這一問(wèn)題而出現(xiàn)。
一個(gè)CascadingValue 內(nèi)的所有組件 包括子級(jí),只要組件屬性上附加了[CascadingParameter]特性,并且值內(nèi)容可以兼容,此屬性就會(huì)被賦值。
比如給組件定義 屬性接收CascadingValue
[CascadingParameter] public int Value { get; set; }
[CascadingParameter] public string SValue { get; set; }
//修改下輸出
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddMarkupContent(0, $"<div>CascadingValue: {Value},{SValue} </div>");// 一個(gè)int,一個(gè)string
ChildContent?.Invoke(this)(builder);//加載下級(jí)組件
base.BuildRenderTree(builder);
}
在razor頁(yè)中
<CascadingValue Value="123"> //int
<MyComp>
<MyComp></MyComp>
</MyComp>
</CascadingValue >
執(zhí)行后我們就會(huì)發(fā)現(xiàn),兩個(gè)組件都捕獲到了int 值 123.
現(xiàn)在再加一個(gè)CascadingValue
<CascadingValue Value="123"> //int
<CascadingValue Value="@("aaaa")"> //string
<MyComp>
<MyComp></MyComp>
</MyComp>
</CascadingValue >
</CascadingValue >
分屬兩個(gè)CascadingValue 的兩個(gè)不同類型值,就被每個(gè)組件的兩個(gè)屬性捕獲到,方便、強(qiáng)大而且自身不產(chǎn)生任何HTML輸出,因此使用場(chǎng)景非常廣泛。比如官方Forms組件中就是借助CascadingValue/Parameter 完成model的設(shè)置,再比如,組件默認(rèn)沒(méi)有處理父子、包含關(guān)系的接口,這時(shí)就可以簡(jiǎn)單的定義一個(gè)[CascadingParameter] public ComponentBase Parent{get;set;}專門接收父級(jí)組件,處理類似Table/Columns之類的組件關(guān)系。
五、總結(jié)
組件是為其自身的 BuildRenderTree方法 ( RenderFragment )服務(wù)的,組件上的各種屬性方法,都是為了給RenderFragment 做環(huán)境準(zhǔn)備,因此組件實(shí)質(zhì)上是個(gè)RenderFragment的包裝類。組件系統(tǒng)則通過(guò)一個(gè)RenderTreeBuilder依次調(diào)用各組件,收集輸出內(nèi)容,最終交給系統(tǒng)內(nèi)部完成輸出。
1、.Razor文件會(huì)被編譯為一個(gè)組件類(obj/debug/netcore3.0/razor/...)
2、組件系統(tǒng)創(chuàng)建RenderTreeBuilder,將其交給組件實(shí)例
3、組件實(shí)例使用 RenderTreeBuilder,調(diào)用自身 BuildRenderTree。
4、等待組件狀態(tài)變化,再次輸出。
原文連接:https://www.cnblogs.com/cerl/p/11834510.html
總結(jié)
以上是生活随笔為你收集整理的Blazor 服务端组件 Render, RenderFragment ,RenderTreeBuilder, CascadingValue/CascadingParameter的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 安装nb-extention插件(Jup
- 下一篇: 使用VESA示例