使用 RenderTreeBuilder 創(chuàng)建組件是 Blazor 的一種高級(jí)方案。前幾篇文中有這樣創(chuàng)建組件的示例 builder.Component<MyComponent>().Build();
付呕,本文主要介紹該高級(jí)方案的具體實(shí)現(xiàn)胯甩,我們采用測(cè)試驅(qū)動(dòng)開發(fā)(TDD)方法入桂,大致思路如下:
- 從測(cè)試示例入手
- 擴(kuò)展一個(gè)RenderTreeBuilder類的泛型擴(kuò)展方法哟旗,泛型類型為組件類型
- 創(chuàng)建組件建造者類(ComponentBuilder)提供方法來構(gòu)建組件
- 通過組件的屬性選擇器來設(shè)置組件參數(shù)
- 構(gòu)建時(shí)能返回組件的對(duì)象實(shí)例
1. 示例
首頁我們從一個(gè)我們預(yù)想的高級(jí)方案示例入手箱吕,然后逐漸分析并實(shí)現(xiàn)我們預(yù)想的方案缸棵。下面是預(yù)想的示例代碼:
class MyComponent : ComponentBase
{
private MyTest test; //MyTest組件的對(duì)象實(shí)例
//覆寫構(gòu)建呈現(xiàn)樹方法
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.Component<MyTest>()
.Set(c => c.Title, "Hello") //設(shè)置MyTest組件Title參數(shù)
.Build(value => test = value); //建造組件并給MyTest實(shí)例賦值
}
}
2. 擴(kuò)展方法
下面實(shí)現(xiàn)builder.Component<MyTest>()
這行代碼舟茶,這是RenderTreeBuilder
的一個(gè)擴(kuò)展方法,該方法返回組件建造者類(ComponentBuilder)堵第。
public static class Extension
{
//泛型T是Blazor組件類型
public static ComponentBuilder<T> Component<T>(this RenderTreeBuilder builder) where T : notnull, IComponent
{
//返回一個(gè)組件建造者類對(duì)象吧凉,將builder傳遞給建造者
//其內(nèi)部方法需要通過builder來構(gòu)建組件
return new ComponentBuilder<T>(builder);
}
}
3. 建造者類
接下來實(shí)現(xiàn)組件建造者類(ComponentBuilder),該類是手動(dòng)構(gòu)建組件的核心代碼踏志,提供設(shè)置組件參數(shù)以及構(gòu)建方法阀捅。
public class ComponentBuilder<T> where T : IComponent
{
//手動(dòng)構(gòu)建呈現(xiàn)器
private readonly RenderTreeBuilder builder;
//組件參數(shù)字典,設(shè)置組件參數(shù)時(shí)针余,先存入字典饲鄙,在構(gòu)建時(shí)批量添加
internal readonly Dictionary<string, object> Parameters = new(StringComparer.Ordinal);
//構(gòu)造函數(shù)
internal ComponentBuilder(RenderTreeBuilder builder)
{
this.builder = builder;
}
//添加組件參數(shù)方法,name為組件參數(shù)名稱圆雁,value為組件參數(shù)值
//提供Add方法可以添加非組件定義的屬性忍级,例如html屬性
public ComponentBuilder<T> Add(string name, object value)
{
Parameters[name] = value; //將參數(shù)存入字典
return this; //返回this對(duì)象,可以流式操作
}
//設(shè)置組件參數(shù)方法伪朽,selector為組件參數(shù)屬性選擇器表達(dá)式轴咱,value為組件參數(shù)值
//使用選擇器有如下優(yōu)點(diǎn):
// - 當(dāng)組件屬性名稱更改時(shí),可自動(dòng)替換
// - 通過表達(dá)式 c => c. 可以直接調(diào)出組件定義的屬性,方便閱讀
// - 可通過TValue直接限定屬性的類型嗦玖,開發(fā)時(shí)即可編譯檢查
public ComponentBuilder<T> Set<TValue>(Expression<Func<T, TValue>> selector, TValue value)
{
var property = TypeHelper.Property(selector); //通過屬性選擇器表達(dá)式獲取組件參數(shù)屬性
return Add(property.Name, value); //添加組件參數(shù)
}
//組件構(gòu)建方法患雇,action為返回組件對(duì)象實(shí)例的委托跃脊,默認(rèn)為空不返回實(shí)例
public void Build(Action<T> action = null)
{
builder.OpenComponent<T>(0); //開始附加組件
if (Parameters.Count > 0)
builder.AddMultipleAttributes(1, Parameters); //批量添加組件參數(shù)
if (action != null)
builder.AddComponentReferenceCapture(2, value => action.Invoke((T)value)); //返回組件對(duì)象實(shí)例
builder.CloseComponent(); //結(jié)束附加組件
}
}
4. 屬性選擇器
為什么要用屬性選擇器宇挫,組件建造者類中已經(jīng)提到,下面介紹如何通過屬性選擇器表達(dá)式來獲取組件類型的屬性對(duì)象酪术。
public class TypeHelper
{
//通過屬性選擇器表達(dá)式來獲取指定類型的屬性
public static PropertyInfo Property<T, TValue>(Expression<Func<T, TValue>> selector)
{
if (selector is null)
throw new ArgumentNullException(nameof(selector));
if (selector.Body is not MemberExpression expression || expression.Member is not PropertyInfo propInfoCandidate)
throw new ArgumentException($"The parameter selector '{selector}' does not resolve to a public property on the type '{typeof(T)}'.", nameof(selector));
var type = typeof(T);
var propertyInfo = propInfoCandidate.DeclaringType != type
? type.GetProperty(propInfoCandidate.Name, propInfoCandidate.PropertyType)
: propInfoCandidate;
if (propertyInfo is null)
throw new ArgumentException($"The parameter selector '{selector}' does not resolve to a public property on the type '{typeof(T)}'.", nameof(selector));
return propertyInfo;
}
}
5. 總結(jié)
以上就是組件建造者的完整實(shí)現(xiàn)過程器瘪,代碼不長(zhǎng),但這些功能足以完成手動(dòng)構(gòu)建Blazor組件的需求绘雁。