一、組件


          支撐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)變化,再次輸出。

          友情鏈接
          ioDraw流程圖
          API參考文檔
          OK工具箱
          云服務(wù)器優(yōu)惠
          阿里云優(yōu)惠券
          騰訊云優(yōu)惠券
          京東云優(yōu)惠券
          站點信息
          問題反饋
          郵箱:[email protected]
          QQ群:637538335
          關(guān)注微信

                亚洲在线大香蕉 | 人人草在线视频 | 国产精品久久777777毛茸茸 | 成人黄网色大免费观看视频 | 四个富婆裸体按摩高潮 |