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