打开MASA Blazor的正确姿势5:插槽/模板

依照Vuetify的习惯,MASABlazor将RenderFragment称为插槽。RenderFragment是Blazor的一个难点,而MASA Blazor是二次开发的组件库,大量使用到插槽,所以有必要掌握这个功能。注:在Blazor中,一般将RenderFragment称为模板。

一、插槽RenderFragment的定义和使用

组件式开发模式,带来了复用、灵活、性能等优势,但也增加了组件之间数据传递的复杂度。组件之间数据传递的方式一般包括以下四种:①通过属性实现父传子,②通过事件实现子传父,③通过插槽实现传递UI片断,④通过状态管理实现跨组件数据共享。

1、默认插槽

//子组件Child.razor
//使用RenderFragment类型的属性参数,接收父组子传递过来的内容
//@ChildContent就像是一个预留的插槽,具体渲染什么内容,由调用Child组件的父组件传入
//父组件在使用Child组件时,传入具体的插槽内容,可以是普通文本、响应式数据、HTML原生元素、自定义组件及其任意组合
//默认插槽,属性名称约定为ChildContent,不能使用其它名称
<div>@ChildContent</div>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}
//父组件Index.razor
//在子组件Child的标签体位置,定义传入子组件插槽中的内容。【<Child>这个位置</Child>】
<Child>普通文本</Child>
<Child>响应式数据:@msg</Child>
<Child>
    <h5>任意HTML原生元素和自定义组件标签</h5>
    <Counter></Counter>
</Child>

@code {
    private string msg = "响应式数据";
}

2、具名插槽

//子组件Child.razor
//定义多个RenderFragment类型的属性参数(插槽)
//具名插槽可以任意命名。而默认插槽则只能使用ChildContent
<div>@Header</div>
<div>@Body</div>
<div>@Footer</div>
<div>@ChildContent</div>

@code {
    [Parameter]
    public RenderFragment? Header { get; set; }
    [Parameter]
    public RenderFragment? Body { get; set; }
    [Parameter]
    public RenderFragment? Footer { get; set; }
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}
//父组件Index.razor
//使用标签方式传递具名插槽,标签名就是子组件的插槽名。
<Child>
    <Header>
        <h1>这里是Header片断</h1>
    </Header>
    <Body>
        <h3>这里是Body片断</h3>
    </Body>
    <Footer>
        <h5>这里是Footer片断</h5>
    </Footer>
    <h1>其它未具名的内容,都由默认插槽ChildContent接收</h1>
</Child>

@code {
}

3、RenderFragment的本质

  • RenderFragment的本质是一个委托,参数是RenderTreeBuilder对象
  • 源码【public delegate void RenderFragment(RenderTreeBuilder builder);】
  • RenderTreeBuilder是Blazor中用于渲染UI的对象
  • 在视图层调用@HelloContent,实际上就是在调用委托,而这个委托执行RenderTreeBuilder的一系列渲染UI的方法
  • RenderFragment委托的方法体,正是我们在默认插槽或具体插槽中传入组件的内容。
//以下三种方法是等效的

//方法一:
<div><h1>hello world</h1></div>

//方法二:
<div>@HelloContent</div>
@code{
    private RenderFragment HelloContent=(RenderTreeBuilder builder)=>{
        builder.OpenElement(0,"h1");
        builder.AddContent(1, "hello world");
        builder.CloseElement();
    };
}

//方法三:
//注意这种方式,参数名称必须为__builder
<div>@HelloContent</div>
@code {
    private RenderFragment HelloContent = (RenderTreeBuilder __builder) =>
    {
        <h1>hello world </h1>
    };
}

4、更复杂的泛型RenderFragment<T>的本质

  • RenderFragment<TValue>的本质可以理解为是委托的委托,返回值是一个RenderFragment
  • 源码【public delegate RenderFragment RenderFragment(TValue value);】
  • 在视图层调用@HelloContent("world"),将实参传入HelloContent委托
  • 通过这种方法,可以实现将数据从插槽传到父组件。
  • 泛型参数T可以是任意类型。
//将参数传入到委托的方法体中(如上我们知道,方法体中执行的一系列UI渲染,就是从父组件传入到插槽的内容)
//HelloContent委托,定义形参msg
//调用@HelloContent()时,传入实参
<div>@HelloContent("world")</div>
@code {
    private RenderFragment<string> HelloContent = (msg) => (RenderTreeBuilder builder) =>
    {
        builder.OpenElement(0, "div");
        builder.AddContent(1, "hello " + msg);
        builder.CloseElement();
    };
}

5、泛型RenderFragment<T>的小案例

//子组件Child.razor
@ChildContent(msg)

@code {
    [Parameter]
    public RenderFragment<string>? ChildContent { get; set; }
    private string msg = "hello world";
}
//父组件Index.razor
//获取子组件传递过来的msg,有两种方式
//方式一:通过@context
<Child>
    <div>
        @context
    </div>
</Child>

/*方法二:通过定义Context属性
<Child Context="msg"> //msg可以任意命名
    <div>
        @msg
    </div>
</Child>
*/

6、泛型RenderFragment<T>的进一步抽象:泛型参数还可以是泛型,不指定具体类型

//子组件Child.razor
@typeparam T //定义泛型参数T

@ChildContent((T)msg) //将msg强制转化为T泛型

@code {
    [Parameter]
    public RenderFragment<T>? ChildContent { get; set; }
    private object msg = "hello world";
}
//父组件Index.razor
<Child T="string"> //指定泛型类型
    <div>
        @context
    </div>
</Child>



二、MASA Blazor中插槽的应用

1、以简单的Dialogs为例,直接解读代码

<div class="text-center">
    <MDialog @bind-Value="dialog" Width="500">
        <!--具名插槽ActivatorContent
        1、在插槽中使用了@context,说明这个插槽是一个泛型RenderFragment<T>
        2、查看组件API,可以看到插槽的类型为RenderFragment<ActivatorProps>
        3、ActivatorProps类定义了一个字典类型属性Attrs,ActivatorProps{Dictionary<string,object> Attrs}
        4、MDialog组件或其继承类,创建ActivatorProps对象,并通过@ActivatorContent(activatorProps),传递ActivatorProps对象
        5、@attributes,属性展开。属性值须为字典类型,展开后key为属性名,value为属性值。
        6、@attributes="@context.Attrs",读取字典值,展开后,设置为MButton组件的属性。
        -->
        <ActivatorContent>
            <MButton
                Color="red lighten-2"
                Dark
                @attributes="@context.Attrs">
                Click Me
            </MButton>
        </ActivatorContent>

        <!--默认插槽ChildContent-->
        <ChildContent>
            <MCard>
                <MCardTitle Class="text-h5 grey lighten-2">卡片标题</MCardTitle>
                <MCardText>卡片内容</MCardText>
            </MCard>
        </ChildContent>
    </MDialog>
</div>
Dialog的API文档-插槽部分:

2、插槽应用最复杂的应该是Table组件,有机会可以读一下源码


posted @ 2023-03-04 17:23  functionMC  阅读(700)  评论(1编辑  收藏  举报