.NET8 Blazor 從入門到精通:(二)元件

二次元攻城狮發表於2024-08-16

目錄
  • Blazor 元件
    • 基礎
    • 路由導航
    • 引數
      • 元件引數
      • 路由引數
    • 生命週期事件
    • 狀態更改
    • 元件事件

Blazor 元件

基礎

新建一個專案命名為 MyComponents ,專案模板的互動型別選 Auto ,其它保持預設選項:
image

客戶端元件 (Auto/WebAssembly):
最終解決方案裡面會有兩個專案:伺服器端專案客戶端專案,元件按存放專案的不同可以分為以下兩種元件:

  • 伺服器端元件:

    • 主要用於伺服器端渲染(SSR)
    • 被放置在伺服器端專案中
    • 適用於不需要實時互動或複雜使用者互動的場景
  • 客戶端元件 (Auto/WebAssembly):

    • 元件位於客戶端專案內
    • 使用 WebAssembly 技術進行編譯,能夠直接與瀏覽器互動
    • 適合需要互動性和實時更新的應用場景
    • 使用 SignalR 可以實現實時通訊,從而增強元件的功能性

兩種元件選擇原則如下:

  • 如果元件不需要互動性,將其作為伺服器端渲染的元件。
  • 如果元件需要互動性(例如響應使用者的輸入、實時資料更新等),則應該考慮將其作為客戶端元件,可以利用 SignalR 提供的實時通訊功能。

在客戶端專案中新建一個 Demo 元件:

<!-- 選擇 Auto 或 WebAssembly ,否則無法互動 -->
@rendermode InteractiveAuto

<h3>Demo</h3>

<!-- 文字不為空時才顯示標籤 -->
@if (textInfo is not null)
{
    <h4>Info: @textInfo</h4>
}

<!-- 按鈕樣式參考 Counter 元件 -->
<button class="btn btn-primary" @onclick="UpdateText">Update Text</button>
<!-- 委託方式呼叫方法,可以傳入引數 -->
<button class="btn btn-primary" @onclick="(()=>{UpdateNumber(10);})">Update Number</button>


@code {
    private string? textInfo = null;

    private void UpdateText()
    {
        textInfo = "This is the new information";
    }

    private void UpdateNumber(int i = 0)
    {
        textInfo = $"This is number {i}";
    }
}

在伺服器端專案中的 Home 頁面中引用 Demo 元件:

@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<Demo />

在 _Imports.razor 中引用 Demo 元件的名稱空間:

@using MyComponents.Client.Pages

路由導航

在客戶端專案中新增一個 Start 元件,razor 程式碼如下:

@*元件可以同時有多個路由*@
@page "/page"
@page "/pages/start"

@*可以使用元件名作路由*@
@attribute [Route(nameof(Start))]

@*頁面跳轉必須指定互動性,並注入導航管理器*@
@rendermode InteractiveAuto
@inject NavigationManager Navigation

<h3>Start</h3>

@*透過NavigationManager.NavigateTo方法跳轉到Counter元件*@
<button class="btn btn-primary" onclick="@(()=>Navigation.NavigateTo(nameof(Counter)))">Go to Counter</button>

@*執行完整的頁面重新載入*@
<button class="btn btn-primary" onclick="@(()=>Navigation.Refresh(true))">Refresh</button>
@code {

}

上面的程式碼演示瞭如何使用路由和導航管理器進行頁面跳轉,元件可以同時有多個路由,也可以使用元件名作路由
跳轉到其它元件時會用到增強導航,參考 增強的導航和表單處理

引數

元件引數

在客戶端專案中新增一個 BetterCounter 元件,razor 程式碼如下:

@rendermode InteractiveAuto

<h3>BetterCounter</h3>
<p role="status">Current count: @CurrentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {

    //將目標成員表示為元件引數
    [Parameter]
    public int CurrentCount { get; set; }

    private void IncrementCount()
    {
        CurrentCount++;
    }
}

元件引數將資料傳遞給元件,使用元件類中包含 [Parameter] 特性的公共 C# 屬性進行定義,參考元件引數單向繫結

使用元件時需要將目標成員作為元件引數傳遞給元件,如在 Home 頁面中引用 BetterCounter 元件:

<BetterCounter CurrentCount="100" />
<!-- 或 -->
<BetterCounter CurrentCount=@currentCount />

路由引數

路由器使用路由引數以相同的名稱填充相應的元件引數,路由引數名不區分大小寫,參考文件 路由引數

在 BetterCounter 元件的 razor 程式碼中新增如下路由:

@*路由引數(無約束)*@
@page "/BetterCounter/{CurrentCount}"

@*路由引數約束*@
@page "/BetterCounter/{CurrentCount:int}"

@*可選路由引數(與上面任意一個路由搭配使用實現可選效果)*@
@page "/BetterCounter"

為了實現可選路由引數,需要在元件中新增一個預設值:

//將目標成員表示為元件引數
//需要將引數屬性的型別更改為可為 null,這樣就可以分辨出它是否被指定了值
[Parameter]
public int? CurrentCount { get; set; }

//為可選引數指定預設值    
protected override void OnInitialized()
{
    base.OnInitialized();    
    CurrentCount = CurrentCount ?? 1;
}

新增一個 Titlet 成員來接受查詢字串的引數:


//指定元件引數來自查詢字串
//路由:/BetterCounter?Titlet=asd
[SupplyParameterFromQuery]
public string? Titlet { get; set; } = "BetterCounter";
  • 路由引數:在新增元件的 @page 宣告時,透過將路由引數的名稱括在一對 { 大括號 } 中,在URL中定義了路由引數,參考示例 路由引數

  • 路由引數約束:以冒號為字尾,然後是約束型別,約束型別參考文件 路由約束。以路由 /BetterCounter/abs 為例:

    • 沒有路由約束時會報型別轉換異常(字串無法轉為int型別)
    • 有路由約束時會顯示404錯誤(沒有匹配到該 URL)
  • 可選路由引數:Blazor不明確支援可選路由引數,但透過在元件上新增多個 @page 宣告,可以輕鬆實現等效的路由引數,參考文章 可選路由引數

  • 查詢字串:使用 [SupplyParameterFromQuery] 屬性指定元件引數來自查詢字串,更多應用場景參考文件 查詢字串

生命週期事件

以下簡化圖展示了 Razor 元件生命週期事件處理,參考文件 生命週期事件

image

在 Counter 元件中新增日誌記錄,觀察元件的生命週期:

@inject ILogger<Counter> log

//...

@code {
    //...
    protected override void OnInitialized()
    {
        log.LogInformation($"Initialized at {DateTime.Now}");
    }
    protected override void OnParametersSet()
    {
        log.LogInformation($"ParametersSet at {DateTime.Now}");
    }
    protected override void OnAfterRender(bool firstRender)
    {
        log.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }
}
  • 元件初始化 (OnInitialized{Async}) :專門用於在元件例項的整個生命週期內初始化元件,引數值和引數值更改不應影響在這些方法中執行的初始化。

  • 設定引數之後 (OnParametersSet{Async}):在 OnInitialized 或 OnInitializedAsync 中初始化元件後或父元件重新呈現並變更引數時呼叫。

  • 元件呈現之後 (OnAfterRender{Async}):OnAfterRender 和 OnAfterRenderAsync 元件以互動方式呈現,並在 UI 已完成更新(例如,元素新增到瀏覽器 DOM 之後)後呼叫。OnAfterRender 和 OnAfterRenderAsync 的 firstRender 引數:

    • 在第一次呈現元件例項時設定為 true。
    • 可用於確保初始化操作僅執行一次。

執行後導航到Counter介面,控制檯輸出如下:
image

狀態更改

StateHasChanged 通知元件其狀態已更改。 如果適用,呼叫 StateHasChanged 會導致元件重新呈現

將自動為 EventCallback 方法呼叫 StateHasChanged,也可以根據實際需求在元件中手動呼叫 StateHasChanged:

private async void IncrementCount()
{
    currentCount++;
    await Task.Delay(1000);
    StateHasChanged();

    currentCount++;
    await Task.Delay(1000);
    StateHasChanged();

    currentCount++;
    await Task.Delay(1000);
    StateHasChanged();
}

上面的程式碼,如果不呼叫 StateHasChanged 則點選後只會顯示 1 ,用 StateHasChanged 後則會依次顯示 1 2 3 。

元件事件

巢狀元件的常見方案是在發生子元件事件時在父元件中執行某個方法(如子元件中的 onclick 事件),跨元件公開事件請使用 EventCallback ,父元件可向子元件的 EventCallback 分配回撥方法。

在 Counter 元件中新增一個事件:

@code {
    private int currentCount = 0;

    //定義一個事件回撥引數
    [Parameter]
    public EventCallback<int> OnCounterChange { get; set; }

    private async Task IncrementCount()
    {
        currentCount++;
        //觸發事件回撥
        await OnCounterChange.InvokeAsync(currentCount); 
    }
}

在另一個客戶端元件為 Counter 元件的事件分配回撥方法(服務端元件會報錯):

<Counter OnCounterChange="UpdateCounter" />

@code {
    private int currentCount = 0;
    private void UpdateCounter(int val)
    {
        currentCount = val;
    }
}

相關文章