再学Blazor——扩展方法

上篇提到 Blazor 组件的高级写法,是采用扩展方法对 HTML 元素和组件进行扩展,以便于书写组件结构和代码阅读。本篇主要介绍扩展方法实现的思路。

  • 什么是扩展方法
  • 要扩展哪个类
  • 扩展方法的实现

1. 什么是扩展方法

若要对一个 C# 类型添加新方法,一是修改源码,二是派生类,三是扩展方法。前两者不是万能的,第一种我们不一定有源码,第二种类型不一定能继承,只有第三种是万能的方法,在项目中新建一个扩展类型即可对任何类型进行扩展。

一个扩展方法需要有如下条件:

  • 添加扩展类,类必须声明 static 修饰符
  • 添加方法,方法必须声明 static 修饰符
  • 方法第一个参数必须是扩展类型,且要有 this 关键字
csharp 复制代码
//扩展类型
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 来管理元素属性。建造者类型如下:

csharp 复制代码
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 元素,代码示例如下:

csharp 复制代码
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# 代码进行比对,直观感受一下高级写法的妙处。

csharp 复制代码
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>
}

再次优化一下扩展方法的示例,下面代码看起来是不是更整齐了一些。

csharp 复制代码
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>
}
相关推荐
2601_9657984720 分钟前
Arrow Unlocker HTML5 Game Review: Keep Web Arcade Players Coming Back
前端·html·html5
不简说1 小时前
JS 代码技巧 vol.10 — 20 个 V8 引擎原理,解释"为什么这样写快"
前端·javascript·面试
醉城夜风~1 小时前
HTML常用标签详解学习博客:从基础标签到页面结构完整掌握
前端·学习·html
OpenTiny社区1 小时前
拒绝臃肿!TinyRobot从“AI 输入框”到“AI输入控制台”的可扩展进化
前端·vue.js
淼澄研学2 小时前
PyTorch深度学习实战:5个核心方法从0到1构建神经网络
前端·数据库·python
MartinYeung52 小时前
[论文学习]WASP:面向提示注入攻击的Web代理安全性基准测试
前端·网络·学习
lemon_sjdk2 小时前
从 ServletRequest 到 Spring 抽象:Web 请求的底层基石与演化
java·前端·spring
程序员黑豆3 小时前
鸿蒙应用开发之状态变化通知:@Watch 装饰器详解与实战
前端·harmonyos
swipe3 小时前
13|(前端转全栈)支付成功不等于结束:回调、幂等、超时关单和状态竞争
前端·后端·面试
swipe3 小时前
12|(前端转全栈)点击提交订单后,后端如何用事务守住价格、库存和订单?
前端·后端·面试