上篇提到 Blazor 元件的高階寫法,是採用擴充套件方法對 HTML 元素和元件進行擴充套件,以便於書寫元件結構和程式碼閱讀。本篇主要介紹擴充套件方法實現的思路。
若要對一個 C# 型別新增新方法,一是修改原始碼,二是派生類,三是擴充套件方法。前兩者不是萬能的,第一種我們不一定有原始碼,第二種型別不一定能繼承,只有第三種是萬能的方法,在專案中新建一個擴充套件型別即可對任何型別進行擴充套件。
一個擴充套件方法需要有如下條件:
//擴充套件型別
static class Extension
{
static void SayHello (this string name)
{
Console.WriteLine($"Hello, I'm {name}.");
}
}
//測試
var name = "Known";
name.SayHello();
//輸出
Hello, I'm Known.
上篇提到元件的高階寫法,需要覆寫元件的 BuildRenderTree 方法,這個方法有唯一型別引數 RenderTreeBuilder,對,就是這個型別,我們從它開始擴充套件一切 HTML 基本元素及自定義元件。
下面看看 RenderTreeBuilder 有哪些原生方法。
<div>
等開始標籤</div>
等關閉標籤<MyComponent>
</MyComponent>
掌握以上這些方法的使用後,我們就可以開發擴充套件我們需要的元素和元件。
由於 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>
}