一、组件

支撑Blazor的是微软的两大成熟技术,Razor模板和SignalR,两者的交汇点就是组件。通常,我们从ComponentBase派生的类型,或者创建的.razor 文件,就可以称作组件。基于这两大技术,组件也就具备了两大功能,1、生成html片段;2、维护组件状态。这里我们来说一下组件最基本的功能,生成html片段。

二、RenderTreeBuilder,RenderFragment

我们知道,浏览器处理HTML 文档时会将所有的标签都挂到一颗文档树中,无论一段HTML来自哪里,总会被这棵树安排的明明白白。换句话说,如果有根线的话,我们可以依靠这棵树把所有的标签都串起来,而在Blazor组件中也有这么一根线,这根线就是RenderTreeBuilder,拿这根线的人就是Blazor框架。

备注一下:以下涉及的代码如果没有特别说明,都是指写在.cs文件中,继承 Microsoft.AspNetCore.Components.ComponentBase 的组件类。

下面用代码看看这根线。 新建一个Blazor 应用 项目,新增 一个c#类,MyComp 继承 Microsoft.AspNetCore.Components.ComponentBase,然后override 一下,找到如下方法:

 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 可以做什么。

  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>.

在标题里我们提到了RenderFragment, 查看一下它的定义。

public delegate void RenderFragment(RenderTreeBuilder builder);//还是一个 Action<RenderTreeBuilder>,或者说,BuildRenderTree 就是一个RenderFragment

我们发现和前面的BuildRenderTree 在签名上一模一样,既然blazor会使用RenderTreeBuilder 去调用BuildRenderTree 方法,那么RenderFragment会不会也被调用?

让我们暂时离开组件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 , 即 Func<TValue,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 下,可以多打开看看。

三、RenderFragment 的一些用法

1、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 是特殊名称

2、组件上有多个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,可以看看结果。

3、带参数的RenderFragment

code:

[Parameter] public RenderFragment<MyComp> ChildContent { get; set; }

调用及传参

  <MyComp Context="self" > //<ChildContent>
@self.GetType() </MyComp> //</ChildContent>

4、打开的组件声明标记内部,除了可以使用RenderFragment 参数属性外,其他的razor 语法基本都支持,也包括另外一个组件。

比如

  <MyComp>
<CompA />
<CompB> ...... </CompB>
</MyComp>

或者

  <MyComp>
<Fragment1>
<CompA />
</Fragment1> <Fragment2>
<CompB> ...... </CompB>
</Fragment2>
</MyComp>

虽然看上去,声明标记的代码很相似,但却有着实质上的不同。

当我们使用 标记声明一个参数属性时,我们是在生成RenderFragment,随后将其赋值给对应的属性。

当我们使用标记声明一个组件时,我们是在构造一个组件实例,然后调用它,将组件输出插入到组件所在位置。

参数属性(RenderFragment )属于组件,是组件的一个属性,互相关系是明确的类型《=》成员关系。

组件内部的其他组件标记很多时候只是为了复用一些输出片段,如果不通过代码进行一些处理的话,是无法明确知道组件之间关系的。

四、CascadingValue/CascadingParameter

组件多起来之后,组件之间的数据共享和传递以及组件间的关系就会变的很麻烦,数量少的时候,还可以使用@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依次调用各组件,收集输出内容,最终交给系统内部完成输出。

1、.Razor文件会被编译为一个组件类(obj/debug/netcore3.0/razor/...)

2、组件系统创建RenderTreeBuilder,将其交给组件实例

3、组件实例使用 RenderTreeBuilder,调用自身 BuildRenderTree。

4、等待组件状态变化,再次输出。

[AspNetCore 3.0 ] Blazor 服务端组件 Render, RenderFragment ,RenderTreeBuilder, CascadingValue/CascadingParameter 等等的更多相关文章

  1. Blazor 服务端组件 Render, RenderFragment ,RenderTreeBuilder, CascadingValue/CascadingParameter

    一.组件 支撑Blazor的是微软的两大成熟技术,Razor模板和SignalR,两者的交汇点就是组件.通常,我们从ComponentBase派生的类型,或者创建的.razor 文件,就可以称作组件. ...

  2. [Asp.net core 3.1] 通过一个小组件熟悉Blazor服务端组件开发

    通过一个小组件,熟悉 Blazor 服务端组件开发.github 一.环境搭建 vs2019 16.4, asp.net core 3.1 新建 Blazor 应用,选择 asp.net core 3 ...

  3. 使用 Vue 2.0 实现服务端渲染的 HackerNews

    Vue 2.0 支持服务端渲染 (SSR),并且是流式的,可以做组件级的缓存,这使得极速渲染成为可能.同时, 和 2.0 也都能够配合 SSR 提供同构路由和客户端 state hydration.v ...

  4. Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端

    Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端 目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Se ...

  5. 基于 IOCP 的通用异步 Windows Socket TCP 高性能服务端组件的设计与实现

    设计概述 服务端通信组件的设计是一项非常严谨的工作,其中性能.伸缩性和稳定性是必须考虑的硬性质量指标,若要把组件设计为通用组件提供给多种已知或未知的上层应用使用,则设计的难度更会大大增加,通用性.可用 ...

  6. React(0.13) 服务端渲染的两个函数

    1.React.renderToString 函数,  参数是组件,返回一个字符串 <!DOCTYPE html> <html> <head> <title& ...

  7. Zabbix 5.0:服务端进程总结

    Blog:博客园 个人 参考:<深入理解Zabbix监控系统>.<Zabbix用户手册> Zabbix服务端进程被分为不同的种类,每一种进程负责相应的任务,包括收集原始监控数据 ...

  8. SimpleSSO:使用Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端

    目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Security.SimpleSSO模拟OpenID认证 通过authorization co ...

  9. zabbix系列(一)centos7搭建zabbix3.0.4服务端及配置详解

    1.安装常用的工具软件 yum install -y vim wget centos7关闭防火墙 systemctl stop firewalld.service systemctl disable ...

随机推荐

  1. Android Studio 优秀插件: Parcelable Code Generator

    这里假设我们已经会使用 Parcelable 序列化一个对象了~~ 那么大家会发现 Parcelable 使用起来有些复杂,因为我们要自己复写 几个方法,而且当类的属性比较多的时候,我们就会难受了,又 ...

  2. Linux入门(磁盘与挂载)

    Linux入门之 磁盘管理与挂载   在我们使用计算机或者是服务器时,总会需要接入外部存储的时候,正如我们使用的移动硬盘.U盘.接入手机等,就是一个接入外部存储的过程.上述这些在接入Windows时我 ...

  3. 06-01 DeepLearning-图像识别

    目录 深度学习-图像识别 一.人脸定位 二.手工提取特征的图像分类 2.1 识图认物 2.2 传统分类系统的特征提取 2.3 计算机眼中的图像 2.4 什么是图像特征? 2.5 卷积运算 2.6 利用 ...

  4. django中app分组

    08.13自我总结 django中app分组 一.django路由系统app进行分组 1.创建app 使用pycharm创建django的时候, 加上app的名字,后续多个app只需复制粘贴之前app ...

  5. 使用Swagger服务搭建.Net Core API

    使用Swagger服务搭建.Net Core API 创建.Net Core API 新建项目.文件——新建——项目 选择应用程序模板. 设置存放路径,命名方案名称. 创建API. 搭建成功. 使用S ...

  6. Java编程思想——第17章 容器深入研究 读书笔记(一)

    这一章将学习散列机制是如何工作的,以及在使用散列容器时怎么样编写hashCode()和equals()方法. 一.容器分类 先上两张图 来概况完整的容器分类 再细说都为什么会有那些特性. 二.填充容器 ...

  7. Service Cloud 零基础(一)Case 浅谈

    本片参考:https://resources.docs.salesforce.com/222/latest/en-us/sfdc/pdf/salesforce_case_implementation_ ...

  8. [Luogu2967] 视频游戏的麻烦Video Game Troubles

      农夫约翰的奶牛们游戏成瘾!本来约翰是想要按照调教兽的做法拿她们去电击戒瘾的,可是 后来他发现奶牛们玩游戏之后比原先产更多的奶.很明显,这是因为满足的牛会产更多的奶. 但是,奶牛们在哪个才是最好的游 ...

  9. VoodooPad Mac笔记本

    VoodooPad Mac笔记本 VoodooPad是记录您的笔记和想法的地方.想法,图片,列表,密码和妈妈的苹果派食谱.包括您需要跟踪和组织的所有内容,VoodooPad会与您一起成长而不会妨碍您. ...

  10. 第一个shell脚本(一)

    第一个脚本 [root@ipha-dev71- exercise_shell]# ll total -rw-r--r-- root root Aug : test.sh [root@ipha-dev7 ...