再學Blazor——擴充套件方法

2023-10-16 12:01:04

上篇提到 Blazor 元件的高階寫法,是採用擴充套件方法對 HTML 元素和元件進行擴充套件,以便於書寫元件結構和程式碼閱讀。本篇主要介紹擴充套件方法實現的思路。

  • 什麼是擴充套件方法
  • 要擴充套件哪個類
  • 擴充套件方法的實現

1. 什麼是擴充套件方法

若要對一個 C# 型別新增新方法,一是修改原始碼,二是派生類,三是擴充套件方法。前兩者不是萬能的,第一種我們不一定有原始碼,第二種型別不一定能繼承,只有第三種是萬能的方法,在專案中新建一個擴充套件型別即可對任何型別進行擴充套件。

一個擴充套件方法需要有如下條件:

  • 新增擴充套件類,類必須宣告 static 修飾符
  • 新增方法,方法必須宣告 static 修飾符
  • 方法第一個引數必須是擴充套件型別,且要有 this 關鍵字
//擴充套件型別
static class Extension
{
    static void SayHello (this string name)
    {
        Console.WriteLine($"Hello, I'm {name}.");
    }
}

//測試
var name = "Known";
name.SayHello();

//輸出
Hello, I'm Known.

2. 要擴充套件哪個類

上篇提到元件的高階寫法,需要覆寫元件的 BuildRenderTree 方法,這個方法有唯一型別引數 RenderTreeBuilder,對,就是這個型別,我們從它開始擴充套件一切 HTML 基本元素及自定義元件。

下面看看 RenderTreeBuilder 有哪些原生方法。

  • OpenElement,開啟一個元素,呈現 <div> 等開始標籤
  • CloseElement,關閉一個元素,呈現 </div> 等關閉標籤
  • OpenComponent,開啟一個元件,呈現 <MyComponent>
  • CloseComponent,關閉一個元件,呈現 </MyComponent>
  • AddAttribute,新增元素和元件的屬性,有8個過載方法
  • AddMultipleAttributes,一次性新增多個屬性,引數為字典型別
  • AddContent,新增標籤內部的內容,有6個過載方法
  • AddMarkupContent,新增原始 HTML 字元內容
  • AddElementReferenceCapture,新增元素物件參考,通過它可獲取元素物件的範例
  • AddComponentReferenceCapture,新增元件物件參考,通過它可獲取元件物件的範例

掌握以上這些方法的使用後,我們就可以開發擴充套件我們需要的元素和元件。

3. 擴充套件方法的實現

由於 HTML 元素標籤及其屬性眾多,為了方便且全面適配所有屬性,增加一個屬性建造者型別 AttributeBuilder 來管理元素屬性。建造者型別如下:

public class AttributeBuilder
{
    private readonly RenderTreeBuilder builder;

    public AttributeBuilder(RenderTreeBuilder builder)
    {
        this.builder = builder;
    }

    public AttributeBuilder Add(string name, object value)
    {
        if (value != null)
            builder.AddAttribute(1, name, value);
        return this;
    }

    public AttributeBuilder Id(string id) => Add("id", id);
    public AttributeBuilder Name(string name) => Add("name", name);
    ...
}

新增一個 HTML 元素擴充套件類,用於擴充套件 HTML 元素,程式碼範例如下:

public static class HtmlExtension
{
    //通用元素擴充套件方法
    public static void Element(this RenderTreeBuilder builder, string name, Action<AttributeBuilder> child = null)
    {
        builder.OpenElement(0, name);
        var attr = new AttributeBuilder(builder);
        child?.Invoke(attr);
        builder.CloseElement();
    }
    //div標籤
    public static void Div(this RenderTreeBuilder builder, Action<AttributeBuilder> child) => builder.Element("div", child);
    ...
}

下面寫一個元素擴充套件方法的範例,並將呈現的 HTML 結構與 C# 程式碼進行比對,直觀感受一下高階寫法的妙處。

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
    builder.Div(attr =>                     //<div id="myPanel" class="panel">
    {                                       //
        attr.Id("myPanel").Class("panel");  //
        builder.Div(attr =>                 //    <div class="header">
        {                                   //
            attr.Class("header");           //
            //這裡構造 Panel 頭部內容        //
        });                                 //    </div>
        builder.Div(attr =>                 //    <div class="body">
        {                                   //
            attr.Class("body");             //
            //這裡構造 Panel 身體內容        //
        });                                 //    </div>
    });                                     //</div>
}

再次優化一下擴充套件方法的範例,下面程式碼看起來是不是更整齊了一些。

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
    builder.Div("myPanel", "panel", attr => //<div id="myPanel" class="panel">
    {                                       //
        builder.Div("header", attr =>       //    <div class="header">
        {                                   //
            //這裡構造 Panel 頭部內容        //
        });                                 //    </div>
        builder.Div("body", attr =>         //    <div class="body">
        {                                   //
            //這裡構造 Panel 身體內容        //
        });                                 //    </div>
    });                                     //</div>
}