前言
實際專案中總能遇到一個"組件"不是基礎組件但是又會頻繁復用的情況,在開發MASA Auth時也封裝了幾個組件,既有簡單定義CSS樣式和界面封裝的組件(GroupBox),也有帶一定組件內部邏輯的組件(ColorGroup),
本文將一步步演示如何封裝出一個如下圖所示的ColorGroup組件,將MItemGroup改造為ColorGroup,點擊選擇預設的顏色值,
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/css-border-radius-generator/
新建ColorGroup.Razor檔案,代碼如下:
<MItemGroup Mandatory >
<MItem>
<MCard Style="@($"transition: opacity .4s ease-in-out; {(context.Active ? "" : "opacity: 0.5;")}")">
<MButton Fab OnClick="context.Toggle"
Width=20 Height=20 MinWidth=20 MinHeight=20 Color="red">
</MButton>
</MCard>
</MItem>
<MItem>
<MCard Style="@($"transition: opacity .4s ease-in-out; {(context.Active ? "" : "opacity: 0.5;")}")">
<MButton Fab OnClick="context.Toggle"
Width=20 Height=20 MinWidth=20 MinHeight=20 Color="blue">
</MButton>
</MCard>
</MItem>
<MItem>
<MCard Style="@($"transition: opacity .4s ease-in-out; {(context.Active ? "" : "opacity: 0.5;")}")">
<MButton Fab 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 m-color-group d-flex mx-n1 {@Class}")" style="@Style">
@foreach (var color in Colors)
{
<MItem>
<MCard Style="@($"transition: opacity .4s ease-in-out; {(context.Active ? "" : "opacity: 0.5;")}")">
<MButton Fab 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
定義帶引陣列件,使用時默認通過 context
獲取引數值,更多內容參考官方檔案
ColorGroup.Razor檔案中定義插槽位置
<MItem>
<MCard Style="@($"transition: opacity .4s ease-in-out; {(context.Active ? "" : "opacity: 0.5;")}")">
<MButton Fab OnClick="()=>{ context.Toggle();ValueChanged.InvokeAsync(color); }" Width=Size Height=Size MinWidth=Size MinHeight=Size Color="@color">
</MButton>
</MCard>
@if (ItemAppendContent is not null)
{
<div >
@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-cn/aspnet/core/blazor/performance?view=aspnetcore-6.0#define-reusable-renderfragments-in-code
總結
這里只演示了一個ColorGroup很簡單的例子,當然你也可以把這個組件做的足夠“復雜”,其實組件的封裝并沒有想象的那么復雜,無外乎上面提到的四個要素:界面、引數、樣式、插槽,既然有些組件官方不提供,只能自己動手豐衣足食(當然還是希望官方提供更多標準組件之外的擴展組件),
示例專案地址,更多內容參考Masa Blazor 預置組件 實作,
開源地址
MASA.BuildingBlocks:https://github.com/masastack/MASA.BuildingBlocks
MASA.Contrib:https://github.com/masastack/MASA.Contrib
MASA.Utils:https://github.com/masastack/MASA.Utils
MASA.EShop:https://github.com/masalabs/MASA.EShop
MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor
如果你對我們的 MASA Framework 感興趣,無論是代碼貢獻、使用、提 Issue,歡迎聯系我們
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/469767.html
標籤:.NET技术
上一篇:C#Delegate和Control中Invoke和BeginInvoke區別
下一篇:brother S2A資料采集兄弟S500 CNC監控brother S700實時監控兄弟CNC S1000狀態采集