ASP.NET Core Blazor Webassembly 之 元件
阿新 • • 發佈:2020-05-27
## 關於元件
現在前端幾大輪子全面元件化。元件讓我們可以對常用的功能進行封裝,以便複用。元件這東西對於搞.NET的同學其實並不陌生,以前ASP.NET WebForm的使用者控制元件其實也是一種元件。它封裝html程式碼,封裝業務邏輯,對外提供屬性事件等資訊,它完完全全就是個元件,只是使用者控制元件跑在服務端,而現在的元件大多數直接跑在前端。現在Blazor Webassembly微軟正式把元件帶到前端,讓我們看看它是怎麼玩的。
## 第一個元件
廢話不多說下面開始構建第一個元件。這個元件很簡單就是綠色的面板加一個標題的容器,我們就叫它GreenPanel吧。
### 新建Blazor Webassembly專案
前幾天的build大會,Blazor Webassembly已經正式release了。我們更新最新版的Core SDK就會安裝正式版的模板。
[![tCTG2F.md.png](https://s1.ax1x.com/2020/05/25/tCTG2F.md.png)](https://imgchr.com/i/tCTG2F)
新建專案選Blazor Webassembly App專案模板
### 新建GreenPanel元件
在pages命令下新建一個資料夾叫做components,在資料夾下新建一個razor元件,命名為GreenPanel.razor。
> 注意:元件的命名必須大寫字母開頭
[![tCTubn.md.png](https://s1.ax1x.com/2020/05/25/tCTubn.md.png)](https://imgchr.com/i/tCTubn)
新增程式碼如下:
```
Green panel
@code { override void OnInitialized()
{
base.OnInitialized();
}
}
```
一個元件主要是由html,style ,code等組成。html,style用來控制ui表現層,code用來封裝邏輯。
>注意:Blazor目前沒有樣式隔離技術,所以寫在元件內的style有可能會影響其他html元素
### 使用元件
使用元件跟其他框架大體是相同的,直接在需要使用的地方使用以我們元件名作為一個html元素插入:
如果不在同一層目錄下,則需要匯入名稱空間。在_Imports.razor檔案內引用元件的名稱空間:
```
...
@using BlazorWasmComponent.Components
```
在index頁面使用元件:
```
```
執行一下:
[![tCbQUI.md.png](https://s1.ax1x.com/2020/05/25/tCbQUI.md.png)](https://imgchr.com/i/tCbQUI)
### 元件類
每個元件最後都會編譯成一個C#類,讓我們用ILSPy看看一眼長啥樣:
```
// BlazorWasmComponent.Components.GreenPanel
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
public class GreenPanel : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, "\r\n \r\n Green panel\r\n \r\n \r\n \r\n\r\n\r\n");
__builder.AddMarkupContent(1, "");
}
protected override void OnInitialized()
{
base.OnInitialized();
}
}
```
GreenPanel元件會編譯成一個GreenPanel類,繼承自ComponentBase基類。裡面有幾個方法:
1. BuildRenderTree 用來構建html,css等ui元素
2. 其它code部分會也會被合併到這個類裡面
### 生命週期
瞭解元件宣告週期對我們使用元件有很大的幫助。一個元件的聲週期主要依次以下幾個階段:
1. OnInitialized、OnInitializedAsync
2. OnParametersSet、OnParametersSetAsync
3. OnAfterRender、OnAfterRenderAsync
4. Dispose
如果要在每個生命階段插入特定的邏輯,請重寫這些方法:
```
@implements IDisposable
@code {
protected override void OnInitialized()
{
Console.WriteLine("OnInitialized");
base.OnInitialized();
}
protected override Task OnInitializedAsync()
{
Console.WriteLine("OnInitializedAsync");
return base.OnInitializedAsync();
}
protected override void OnParametersSet()
{
Console.WriteLine("OnParametersSet");
base.OnParametersSet();
}
protected override Task OnParametersSetAsync()
{
Console.WriteLine("OnParametersSetAsync");
return base.OnParametersSetAsync();
}
protected override void OnAfterRender(bool firstRender)
{
Console.WriteLine("OnAfterRender");
base.OnAfterRender(firstRender);
}
protected override Task OnAfterRenderAsync(bool firstRender)
{
Console.WriteLine("OnAfterRenderAsync");
return base.OnAfterRenderAsync(firstRender);
}
public void Dispose()
{
Console.WriteLine("Dispose");
}
}
```
> 注意:元件預設並不繼承IDisposable介面,如果要重寫Dispose方法請手工使用@implements方法繼承介面IDisposable
執行一下,並且切換一下頁面,使元件銷燬,可以看到所有生命週期方法依次執行:
[![tCO5in.md.png](https://s1.ax1x.com/2020/05/26/tCO5in.md.png)](https://imgchr.com/i/tCO5in)
## 元件屬性
我們定義元件總是免不了跟外部進行互動,比如從父元件接受引數,或者把自身的資料對外暴露。我們可以使用[Parameter]來定義一個元件的屬性。這裡叫做Parameter,估計是為了跟C#裡的屬性(property,attribute)進行區分。
對我們的GreenPanel元件進行改進,支援從外部定義標題的內容:
```
@Title
@code {
[Parameter]
public string Title { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
}
}
```
在父元件使用:
```
@page "/"
```
執行一下:
[![tFRHvd.png](https://s1.ax1x.com/2020/05/26/tFRHvd.png)](https://imgchr.com/i/tFRHvd)
上面傳遞的是簡單型別String,下面讓我們試試傳遞複雜型別的資料進去。我們繼續對GreenPanel改造。改造成ColorPanel,它接受一個Setting物件來設定標題跟背景顏色。
定義Setting類:
```
public class PanelSetting
{
public string Title { get; set; }
public string BgColor { get; set; }
}
```
定義ColorPanel:
```
@Setting.Title
@using BlazorWasmComponent.models;
@code {
[Parameter]
public PanelSetting Setting { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
}
}
```
在父元件使用:
```
@page "/"
@using BlazorWasmComponent.models;
@code{
public PanelSetting PanelSetting { get; set; }
public int ClickCount { get; set; }
protected override void OnInitialized()
{
PanelSetting = new PanelSetting
{
BgColor = "Red",
Title = "Panel RED"
};
base.OnInitialized();
}
private void HandleClick()
{
ClickCount++;
}
}
```
執行一下,並點選子元件,父元件的計數器會被+1:
![tFTkSf.png](https://s1.ax1x.com/2020/05/26/tFTkSf.png)
## 子內容
當我們定義容器級別的元件時往往需要往元件內傳遞子內容。比如我們的ColorPanel明顯就有這種需求,這個Panel內部會被放上其它元素或者其它元件,這個時候我們可以使用ChildContent屬性來實現。
```
@Setting.Title
@ChildContent
@using BlazorWasmComponent.models;
@code {
[Parameter]
public PanelSetting Setting { get; set; }
[Parameter]
public EventCallback OnClick { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
}
public void DoClick()
{
OnClick.InvokeAsync(null);
}
}
```
定義一個型別為RenderFragment名稱為ChildContent的屬性,然後在html內使用@ChildContent來指代它。這樣子內容就會被替換到指定的位置。
父元件使用,我們給ColorPanel的內部設定一個文字框吧:
```
@page "/"
@using BlazorWasmComponent.models;
@code{
public PanelSetting PanelSetting { get; set; }
public int ClickCount { get; set; }
protected override void OnInitialized()
{
PanelSetting = new PanelSetting
{
BgColor = "Red",
Title = "Panel RED"
};
base.OnInitialized();
}
private void HandleClick()
{
ClickCount++;
}
}
```
執行一下看看我們的文字框會不會出現在panel內部:
![tF7tv8.png](https://s1.ax1x.com/2020/05/26/tF7tv8.png)
## @ref
因為我們的元件使用是在html內,當你在@code內想要直接通過程式碼操作子元件的時候可以給子元件設定@ref屬性來直接獲取到子元件的物件。繼續改造ColorPanel,在它初始化的時候生產一個ID。
```
@Setting.Title
@ChildContent
@using BlazorWasmComponent.models;
@code {
public string ID { get; set; }
[Parameter]
public PanelSetting Setting { get; set; }
[Parameter]
public EventCallback OnClick { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
protected override void OnInitialized()
{
ID = Guid.NewGuid().ToString();
base.OnInitialized();
}
public void DoClick()
{
OnClick.InvokeAsync(null);
}
}
```
修改父元件,新增一個按鈕,當點選的時候直接獲取子元件的Id:
```
@page "/"
@using BlazorWasmComponent.models;
@code{
private string subId;
private ColorPanel colorPanel;
public PanelSetting PanelSetting { get; set; }
public int ClickCount { get; set; }
protected override void OnInitialized()
{
PanelSetting = new PanelSetting
{
BgColor = "Red",
Title = "Panel RED"
};
base.OnInitialized();
}
private void HandleClick()
{
ClickCount++;
}
private void GetSubComponentId ()
{
this.subId = colorPanel.ID;
}
}
```
執行一下:
![tFLFsK.png](https://s1.ax1x.com/2020/05/26/tFLFsK.png)
## @key
當使用迴圈渲染元件的時候請在元件上使用@key來加速Blazor的diff演算法。有了key就可以快速的區分哪些元件是可以複用的,哪些是要新增或刪除的,特別是在對迴圈列表插入物件或者刪除物件的時候特別有用。如果使用過vue就應該很容易明白有了key可以降低虛擬dom演算法的複雜度,在這裡猜測blazor內部應該也是類似的演算法。
```
@page "/"
@foreach (var key in List)
{
}
@using BlazorWasmComponent.models;
@code{
pub
@PanelSetting.Title
@PanelSetting.BgColor
@using BlazorWasmComponent.models; @code{ public PanelSetting PanelSetting { get; set; } protected override void OnInitialized() { PanelSetting = new PanelSetting { BgColor = "Red", Title = "Panel RED" }; base.OnInitialized(); } } ``` 執行一下: ![tFhaDA.png](https://s1.ax1x.com/2020/05/26/tFhaDA.png) > 注意:上一篇WebAssembly初探裡有個錯誤,當時認為這個屬性是單向資料流,經過試驗子元件對父元件傳入的資料來源進行修改的時候其實是會反應到父元件的,只是如果你使用@符號繫結資料的時候並不會像angularjs,vue等立馬進行重新整理。關於這個事情感覺可以單獨寫一篇,這裡就不細說了。 ## 元件事件 我們的元件當然也可以提供事件,已供外部訂閱,然後從內部激發來通知外部完成業務邏輯,實現類似觀察者模式。繼續改造ColorPanel,當點選時候對外丟擲事件。 使用EventCallback、EventCallback< T > 來定義事件: ``` @Setting.Title @using BlazorWasmComponent.models; @code { [Parameter] public PanelSetting Setting { get; set; } [Parameter] public EventCallback OnClick { get; set; } protected override void OnInitialized() { base.OnInitialized(); } public void DoClick() { OnClick.InvokeAsync(null); } } ``` 父元件訂閱事件: ``` @page "/"子元件點選次數:@ClickCount
子元件點選次數:@ClickCount
子元件ID:@subId