前言

理论我的项目中总能遇到一个"组件"不是根底组件然而又会频繁复用的状况,在开发MASA Auth时也封装了几个组件。既有简略定义CSS款式和界面封装的组件(GroupBox),也有带肯定组件外部逻辑的组件(ColorGroup)。
本文将一步步演示如何封装出一个如下图所示的ColorGroup组件,将MItemGroup革新为ColorGroup,点击抉择预设的色彩值。

<!-- more -->

MASA Blazor介绍

组件展现

MASA Blazor 提供丰盛的组件(还在减少中),篇幅限度上面展现一些我罕用到的组件

Material Design + BlazorComponent

BlazorComponent是一个底层组件框架,只提供性能逻辑没有款式定义,MASA Blazor就是BlazorComponent根底实现了Material Design款式规范。如下图所示,你能够基于Ant Design款式规范实现一套Ant Design Blazor(尽管曾经有了,如果你想这么做齐全能够实现)。

我的项目创立

首先确保已装置Masa Template(防止手动援用MASA Blazor),如没有装置执行如下命令:

dotnet new --install Masa.Template

创立一个简略的Masa Blazor Server App我的项目:

dotnet new masab -o MasaBlazorApp

组件封装

Blazor组件封装很简略,不须要和vue一样进行注册,新建一个XXX.razor组件就是实现了XXX组件的封装,略微简单些的是须要自定义组件外部逻辑以及定义凋谢给用户(不同的应用场景)的接口(参数),即依据需要减少XXX.razor.cs和XXX.razor.css文件。

界面封装

在相熟各种组件性能的前提下找出须要的组件组装起来简略实现想要的成果。这里我应用MItemGroup、MCard及MButton实现ColorGroup的成果。MItemGroup做色彩分组,且自身提供每一项激活的性能。MCard 作为色彩未抉择之前的遮罩层,实现含糊成果。MButton作为色彩展现载体及激活MItem。通过MCard的style设置透明度辨别选中、未选中两种状态。

也可通过减少一个对比色的圆形边框标记选中状态,相干CSS参考:https://www.dailytoolz.com/cs...

新建ColorGroup.Razor文件,代码如下:

<MItemGroup Mandatory Class="m-color-group d-flex mx-n1">    <MItem>        <MCard Class="elevation-0" Style="@($"transition: opacity .4s ease-in-out; {(context.Active ? "" : "opacity: 0.5;")}")">            <MButton Fab class="mx-1 rounded-circle" OnClick="context.Toggle"                     Width=20 Height=20 MinWidth=20 MinHeight=20 Color="red">            </MButton>        </MCard>    </MItem>    <MItem>        <MCard Class="elevation-0" Style="@($"transition: opacity .4s ease-in-out; {(context.Active ? "" : "opacity: 0.5;")}")">            <MButton Fab class="mx-1 rounded-circle" OnClick="context.Toggle"                     Width=20 Height=20 MinWidth=20 MinHeight=20 Color="blue">            </MButton>        </MCard>    </MItem>    <MItem>        <MCard Class="elevation-0" Style="@($"transition: opacity .4s ease-in-out; {(context.Active ? "" : "opacity: 0.5;")}")">            <MButton Fab class="mx-1 rounded-circle" OnClick="context.Toggle"                     Width=20 Height=20 MinWidth=20 MinHeight=20 Color="green">            </MButton>        </MCard>    </MItem></MItemGroup>

批改Index.Blazor 文件 减少ColorGroup应用代码,Masa.Blazor.Custom.Shared.Presets为自定义组件门路,即命名空间:

<Masa.Blazor.Custom.Shared.Presets.ColorGroup></Masa.Blazor.Custom.Shared.Presets.ColorGroup>

运行代码,看到多出三个不同色彩的圆型:

Masa Blazor是Vuetify的Blazor实现,所有的Class除了m-color-group都是Vuetify提供的class款式。

自定义参数

通过第一局部能够看到封装的组件体面(界面)有了,然而这个体面是“死”的,不能依据不同的应用场景展现不同的成果,对于ColorGroup而言,最根本的需要就是应用时能够自定义显示的色彩值。
Blazor中通过[Parameter]个性来申明参数,通过参数的形式将上叙代码中写死的值改为通过参数传入。如按钮的大小、色彩以及MItemGroup的class和style属性等。同时减少组件的里子(组件逻辑),点击不同色彩按钮更新Value。

新建ColorGroup.Razor.cs文件,增加如下代码:

public partial class ColorGroup{    [Parameter]    public List<string> Colors { get; set; } = new();    [Parameter]    public string Value { get; set; } = string.Empty;    [Parameter]    public EventCallback<string> ValueChanged { get; set; }    [Parameter]    public string? Class { get; set; }    [Parameter]    public string? Style { get; set; }    [Parameter]    public int Size { get; set; } = 24;    protected override async Task OnAfterRenderAsync(bool firstRender)    {        if (firstRender)        {            if (Colors.Any())            {                await ValueChanged.InvokeAsync(Colors.First());            }        }        await base.OnAfterRenderAsync(firstRender);    }}
下面的代码能够看到Value参数有个与之对应的ValueChanged参数,目标是为了能在组件内部接管Value值的变更,通过调用ValueChanged.InvokeAsync告诉组件内部Value值更新。

须要留神的是应尽量减少参数定义,太多的参数会减少组件出现的开销。
缩小参数传递,能够自定义参数类(本文示例为独自定义多个参数)。如:

@code {    [Parameter]    public TItem? Data { get; set; }    [Parameter]    public GridOptions? Options { get; set; }}

同时更新ColorGroup.Razor文件中代码,循环Colors 属性显示子元素以及减少MButton的点击事件,更新Value值:

<MItemGroup Mandatory Class="@($"m-color-group d-flex mx-n1 {@Class}")" style="@Style">    @foreach (var color in Colors)    {        <MItem>            <MCard Class="elevation-0" Style="@($"transition: opacity .4s ease-in-out; {(context.Active ? "" : "opacity: 0.5;")}")">                <MButton Fab class="mx-1 rounded-circle" OnClick="()=>{ context.Toggle();ValueChanged.InvokeAsync(color); }"                     Width=Size Height=Size MinWidth=Size MinHeight=Size Color="@color">                </MButton>            </MCard>        </MItem>    }</MItemGroup>

此时应用ColorGroup的代码变为如下代码,能够灵便的指定色彩组数据以及ColorGroup的Class和Style等:

<Masa.Blazor.Custom.Shared.Presets.ColorGroup Colors='new List<string>{"blue","green","yellow","red"}'></Masa.Blazor.Custom.Shared.Presets.ColorGroup>

启用隔离款式

第一局部开端提到了所有的Class除了m-color-group都是Vuetify提供的class款式,那么m-color-group是哪来的?
新增ColorGroup.Razor.css 文件,ColorGroup.Razor.css 文件内的css将被限定在ColorGroup.Razor组件内不会影响其它组件。最终会ColorGroup.Razor.css输入到一个名为{ASSEMBLY NAME}.styles.css的捆绑文件中,{ASSEMBLY NAME} 是我的项目的程序集名称。
本文示例并没有减少ColorGroup.Razor.css,只是感觉作为封装组件现有款式够看了,减少m-color-group class 只是为了内部应用时不便css款式重写,并没有做任何定义。

更多隔离款式内容参考官网文档.

自定义插槽

目前为止,自定义的ColorGroup组件能够说曾经够看了,然而不够打。因为模式繁多,如果要在色彩抉择按钮后减少文本或者图片怎么办?这就又引入另外一个概念:插槽。
插槽(Slot)为vue中的叫法,Vuetify组件提供了大量的插槽如文本输入框内的前后插槽和输入框外的前后插槽(默认为Icon),MASA Blazor 同样实现了插槽的性能,这也使得咱们更容易定义和扩大本人的组件。

Blazor面向C#开发者更违心称之为Template或者Content,通过RenderFragment实现插槽的成果。
若你的组件须要定义子元素,为了捕捉子内容,须要定义一个名为ChildContent类型为RenderFragment 的组件参数。

ColorGroup.Razor.cs文件中减少RenderFragment属性来定义每项开端追加的插槽,并定义string参数,接管以后的色彩值。

[Parameter]public RenderFragment<string>? ItemAppendContent { get; set; }
RenderFragment<T>定义带参数组件,应用时默认通过context获取参数值。更多内容参考官网文档

ColorGroup.Razor文件中定义插槽地位

<MItem>    <MCard Class="elevation-0" Style="@($"transition: opacity .4s ease-in-out; {(context.Active ? "" : "opacity: 0.5;")}")">         <MButton Fab class="mx-1 rounded-circle" OnClick="()=>{ context.Toggle();ValueChanged.InvokeAsync(color); }" Width=Size Height=Size MinWidth=Size MinHeight=Size Color="@color">         </MButton>    </MCard>    @if (ItemAppendContent is not null)    {        <div class="m-color-item-append d-flex align-center mr-1">             @ItemAppendContent(color)        </div>    }</MItem>

最终的成果如下:

组件优化

组件在保障性能和好看的同时,也要保障性能,以下只是列举了一些笔者认为比拟惯例的优化形式。

缩小组件从新渲染

正当重写ShouldRender办法,防止老本昂扬的从新出现。
贴一下官网代码自行领会,即肯定条件都合乎时才从新渲染:

@code {    private int prevInboundFlightId = 0;    private int prevOutboundFlightId = 0;    private bool shouldRender;    [Parameter]    public FlightInfo? InboundFlight { get; set; }    [Parameter]    public FlightInfo? OutboundFlight { get; set; }    protected override void OnParametersSet()    {        shouldRender = InboundFlight?.FlightId != prevInboundFlightId            || OutboundFlight?.FlightId != prevOutboundFlightId;        prevInboundFlightId = InboundFlight?.FlightId ?? 0;        prevOutboundFlightId = OutboundFlight?.FlightId ?? 0;    }    protected override bool ShouldRender() => shouldRender;}

缩小不必要的StateHasChanged办法调用,默认状况下,组件继承自 ComponentBase,会在调用组件的事件处理程序后主动调用StateHasChanged,对于某些事件处理程序可能不会批改组件状态的状况,应用程序能够利用 IHandleEvent 接口来管制 Blazor 事件处理的行为。示例代码见官网文档。

正当重写组件生命周期办法

首先要了解组件生命周期,特地是OnInitialized(组件接管 SetParametersAsync 中的初始参数后调用)、OnParametersSet(接管到参数变更时调用)、OnAfterRender(组件实现出现后调用)。
以上办法每个都会执行两次及以上(render-mode="ServerPrerendered")。
组件初始化的逻辑正当的调配到各个生命周期办法内,最常见的就是OnAfterRender办法内,firstRender为true时调用js或者加载数据:

protected override async Task OnAfterRenderAsync(bool firstRender){    if (firstRender)    {        await JS.InvokeVoidAsync(           "setElementText1", divElement, "Text after render");    }}

OnInitialized生命周期:

  • 在动态预出现组件时执行一次。
  • 在建设服务器连贯后执行一次。
    防止双重出现行为,应传递一个标识符以在预出现期间缓存状态并在预出现后检索状态。

定义可重用的 RenderFragment

将反复的出现逻辑定义为RenderFragment,无需每个组件开销即可重复使用出现逻辑。毛病就是重用RenderFragment短少组件边界,无奈独自刷新。

<h1>Hello, world!</h1>@RenderWelcomeInfo<p>Render the welcome info a second time:</p>@RenderWelcomeInfo@code {    private RenderFragment RenderWelcomeInfo = __builder =>    {        <p>Welcome to your new app!</p>    };}

防止为反复的元素从新创立委托

Blazor 中过多反复的创立 lambda 表达式委托可能会导致性能不佳,如对一个按钮组每个按钮的OnClick调配一个委托。能够将表达式委托改为Action缩小调配开销。

实现IDisposable 或 IAsyncDisposable接口

组件实现IDisposable 或 IAsyncDisposable接口,会在组件从UI中被删除时开释非托管资源,事件登记操作等。

组件不须要同时实现 IDisposable 和 IAsyncDisposable。 如果两者均已实现,则框架仅执行异步重载。

更多内容参考:https://docs.microsoft.com/zh...

总结

这里只演示了一个ColorGroup很简略的例子,当然你也能够把这个组件做的足够“简单”,其实组件的封装并没有设想的那么简单,无外乎下面提到的四个因素:界面、参数、款式、插槽。既然有些组件官网不提供,只能本人入手饥寒交迫(当然还是心愿官网提供更多规范组件之外的扩大组件)。

示例我的项目地址,更多内容参考Masa Blazor 预置组件 实现。

开源地址

MASA.BuildingBlocks:https://github.com/masastack/...

MASA.Contrib:https://github.com/masastack/...

MASA.Utils:https://github.com/masastack/...

MASA.EShop:https://github.com/masalabs/M...

MASA.Blazor:https://github.com/BlazorComp...

如果你对咱们的 MASA Framework 感兴趣,无论是代码奉献、应用、提 Issue,欢送分割咱们