支撑Blazor的是微软的两大成熟技术,Razor模板和SignalR,二者的交汇点就是组件。一般,咱们从ComponentBase派生的类型,或者建立的.razor 文件,就能够称做组件。基于这两大技术,组件也就具有了两大功能,一、生成html片断;二、维护组件状态。这里咱们来讲一下组件最基本的功能,生成html片断。html
咱们知道,浏览器处理HTML 文档时会将全部的标签都挂到一颗文档树中,不管一段HTML来自哪里,总会被这棵树安排的明明白白。换句话说,若是有根线的话,咱们能够依靠这棵树把全部的标签都串起来,而在Blazor组件中也有这么一根线,这根线就是RenderTreeBuilder,拿这根线的人就是Blazor框架。c#
备注一下:如下涉及的代码若是没有特别说明,都是指写在.cs文件中,继承 Microsoft.AspNetCore.Components.ComponentBase 的组件类。浏览器
下面用代码看看这根线。 新建一个Blazor 应用 项目,新增 一个c#类,MyComp
继承 Microsoft.AspNetCore.Components.ComponentBase,而后override 一下,找到以下方法:app
protected override void BuildRenderTree(RenderTreeBuilder builder) { base.BuildRenderTree(builder);//加断点 }
加个断点,在项目的 Pages\Index.razor 里加上一行。<MyComp />
若是不想代码执行两次,就在Pages_Host.cshtml 里修改一下rendermode框架
@(await Html.RenderComponentAsync<App>(RenderMode.Server))
F5跑起来,虽然没有任何输出,可是断点命中了,RenderTreeBuilder这根线确实串起了咱们的组件。
如今让咱们看看,RenderTreeBuilder 能够作什么。ide
protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.AddMarkupContent(0, "<span> BuildRenderTree 使用 AddMarkupContent 输出 Html 。</span>"); // base.BuildRenderTree(builder); }
再次跑起来,咱们发现页面上多了咱们加的span.也就是说HTML的输出,靠的是调用RenderTreeBuilder上的各类方法加上的。组件的基本原理也就是这样,一个RenderTreeBuilder 进入不一样组件的 BuildRenderTree 方法,方法内 经过RenderTreeBuilder上的add.. open.. 方法把咱们想要输出的部分,挂载到builder上,最终输出到浏览器。函数
接下来,咱们考察一下BuildRenderTree
方法, 用委托描述一下,咱们发现这就是一个Action<RenderTreeBuilder>
.ui
在标题里咱们提到了RenderFragment, 查看一下它的定义。this
public delegate void RenderFragment(RenderTreeBuilder builder);//仍是一个 Action<RenderTreeBuilder>,或者说,BuildRenderTree 就是一个RenderFragment
咱们发现和前面的BuildRenderTree
在签名上如出一辙,既然blazor会使用RenderTreeBuilder 去调用BuildRenderTree 方法,那么RenderFragment会不会也被调用?spa
让咱们暂时离开组件MyComp,转到Index.razor 内加一段code
@code{ RenderFragment MyRender=(builder) => builder.AddMarkupContent(0, "<span>当前输出来自:Index.razor 组件, MyRender 字段。 </span>"); }
在以前咱们声明 MyComp组件以后,再加一行调用 @MyRender
.
完整的Index.razor
@page "/" <MyComp /> @MyRender @code{ RenderFragment MyRender = (builder) => builder.AddMarkupContent(0, "<div>当前输出来自:Index.razor 组件, MyRender 字段。 </div>"); }
两段信息,如愿输出,证实blazor可以识别出模板中的 RenderFragment ,并自动调用。
既然咱们在组件模板中(Index.razor)书写RenderFragment ,固然有其余方式能够不用拼凑字符串。
RenderFragment AnotherRender =@<div>模板写法的RenderFragment</div>;
加上调用 @AnotherRender
,跑起来,三段信息。
至此,咱们对RenderFragment 有了一个大概的了解,它是一个函数,内部打包了咱们的输出内容。在模板中咱们可使用,@xxxrender
将其就地展开输出,在c#环境下咱们能够经过 xxxrender(builder)
的形式进行调用(好比在BuildRenderTree方法内调用)。又由于其自己就是一个委托函数,所以咱们便可以在组件内使用,也能够自由的在组件之间传递, 完成对输出内容及逻辑的复用。
同时,为了更好的配合RenderFragment 使用,Blazor中还提供了一个工厂委托,RenderFragment
//模板中(Index.razor) RenderFragment<object> RenderValue =value=> @<div> render value :@value</div>;
调用 @RenderValue (123)
若是在c#代码中,好比在BuildRenderTree 方法内, RenderValue (123)(builder)
。
vs中*.razor在编译时会生成对应的.g.cs代码,位置在obj/debug/netcoreapp3.0/ razor 下,能够多打开看看。
一、html中,咱们能够在一对标签内添加 内容,好比 <div>123</div>
,组件默认是不支持此类操做的,这时咱们就须要RenderFragment来包装标签内的内容。
让咱们回到MyComp组件类中,增长一个属性
[Parameter] public RenderFragment ChildContent{ get; set; }
Index.razor
<MyComp><div> 组件标记内部</div></MyComp>
此时直接运行的话,组件不会输出内部信息,须要在BuildRenderTree 中执行一下
protected override void BuildRenderTree(RenderTreeBuilder builder) { ChildContent?.Invoke(builder); base.BuildRenderTree(builder); }
组件标记内的片断被打包进了 ChildContent,已经变成了独立的一个片断,所以须要咱们显式的调用一下。
ChildContent 是特殊名称
二、组件上有多个RenderFragment
[Parameter] public RenderFragment Fragment1 { get; set; } [Parameter] public RenderFragment Fragment2 { get; set; }
此时调用须要调整一下,否则框架不知道把内容片断打包进哪一个属性里
<MyComp> <Fragment1> <div> Fragment1 </div> </Fragment1> <Fragment1> <div> Fragment1.1 </div> </Fragment1> <Fragment2> <div> Fragment2 </div> </Fragment2> </MyComp>
这里故意重复处理了Fragment1,能够看看结果。
三、带参数的RenderFragment
code:
[Parameter] public RenderFragment<MyComp> ChildContent { get; set; }
调用及传参
<MyComp Context="self" > //<ChildContent> @self.GetType() </MyComp> //</ChildContent>
四、打开的组件声明标记内部,除了可使用RenderFragment 参数属性外,其余的razor 语法基本都支持,也包括另一个组件。
好比
<MyComp> <CompA /> <CompB> ...... </CompB> </MyComp>
或者
<MyComp> <Fragment1> <CompA /> </Fragment1> <Fragment2> <CompB> ...... </CompB> </Fragment2> </MyComp>
虽然看上去,声明标记的代码很类似,但却有着实质上的不一样。
当咱们使用 标记声明一个参数属性时,咱们是在生成RenderFragment,随后将其赋值给对应的属性。
当咱们使用标记声明一个组件时,咱们是在构造一个组件实例,而后调用它,将组件输出插入到组件所在位置。
参数属性(RenderFragment )属于组件,是组件的一个属性,互相关系是明确的类型《=》成员关系。
组件内部的其余组件标记不少时候只是为了复用一些输出片断,若是不经过代码进行一些处理的话,是没法明确知道组件之间关系的。
组件多起来以后,组件之间的数据共享和传递以及组件间的关系就会变的很麻烦,数量少的时候,还可使用@ref 手工指定,多起来以后@ref明显不是一个好方法。 组件CascadingValue和对应的特性[CascadingParameter]就是为了解决这一问题而出现。
一个CascadingValue 内的全部组件 包括子级,只要组件属性上附加了[CascadingParameter]特性,而且值内容能够兼容,此属性就会被赋值。
好比给组件定义 属性接收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 >
执行后咱们就会发现,两个组件都捕获到了int 值 123.
如今再加一个CascadingValue
<CascadingValue Value="123"> //int <CascadingValue Value="@("aaaa")"> //string <MyComp> <MyComp></MyComp> </MyComp> </CascadingValue > </CascadingValue >
分属两个CascadingValue 的两个不一样类型值,就被每一个组件的两个属性捕获到,方便、强大并且自身不产生任何HTML输出,所以使用场景很是普遍。好比官方Forms组件中就是借助CascadingValue/Parameter 完成model的设置,再好比,组件默认没有处理父子、包含关系的接口,这时就能够简单的定义一个[CascadingParameter] public ComponentBase Parent{get;set;}
专门接收父级组件,处理相似Table/Columns之类的组件关系。
组件是为其自身的 BuildRenderTree方法 ( RenderFragment )服务的,组件上的各类属性方法,都是为了给RenderFragment 作环境准备,所以组件实质上是个RenderFragment的包装类。组件系统则经过一个RenderTreeBuilder依次调用各组件,收集输出内容,最终交给系统内部完成输出。 一、.Razor文件会被编译为一个组件类(obj/debug/netcore3.0/razor/...) 二、组件系统建立RenderTreeBuilder,将其交给组件实例 三、组件实例使用 RenderTreeBuilder,调用自身 BuildRenderTree。 四、等待组件状态变化,再次输出。