当前位置: 移动技术网 > IT编程>开发语言>.net > [AspNetCore 3.0 ] Blazor 服务端组件 Render, RenderFragment ,RenderTreeBuilder, CascadingValue/CascadingParameter 等等

[AspNetCore 3.0 ] Blazor 服务端组件 Render, RenderFragment ,RenderTreeBuilder, CascadingValue/CascadingParameter 等等

2019年11月12日  | 移动技术网IT编程  | 我要评论

苏记棺材铺txt,小小阿狸2,永乐英雄儿女演员表

一、组件

支撑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、等待组件状态变化,再次输出。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网