再学Blazor——组件建造者
使用 RenderTreeBuilder 创建组件是 Blazor 的一种高级方案。前几篇文中有这样创建组件的示例 builder.Component<MyComponent>().Build(); ,本文主要介绍该高级方案的具体实现,我们采用测试驱动开发(TDD)方法,大致思路如下:
- 从测试示例入手
- 扩展一个RenderTreeBuilder类的泛型扩展方法,泛型类型为组件类型
- 创建组件建造者类(ComponentBuilder)提供方法来构建组件
- 通过组件的属性选择器来设置组件参数
- 构建时能返回组件的对象实例
1. 示例
首页我们从一个我们预想的高级方案示例入手,然后逐渐分析并实现我们预想的方案。下面是预想的示例代码:
class MyComponent : ComponentBase
{
private MyTest test; //MyTest组件的对象实例
//覆写构建呈现树方法
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.Component<MyTest>()
.Set(c => c.Title, "Hello") //设置MyTest组件Title参数
.Build(value => test = value); //建造组件并给MyTest实例赋值
}
}
2. 扩展方法
下面实现builder.Component<MyTest>()这行代码,这是RenderTreeBuilder的一个扩展方法,该方法返回组件建造者类(ComponentBuilder)。
public static class Extension
{
//泛型T是Blazor组件类型
public static ComponentBuilder<T> Component<T>(this RenderTreeBuilder builder) where T : notnull, IComponent
{
//返回一个组件建造者类对象,将builder传递给建造者
//其内部方法需要通过builder来构建组件
return new ComponentBuilder<T>(builder);
}
}
3. 建造者类
接下来实现组件建造者类(ComponentBuilder),该类是手动构建组件的核心代码,提供设置组件参数以及构建方法。
public class ComponentBuilder<T> where T : IComponent
{
//手动构建呈现器
private readonly RenderTreeBuilder builder;
//组件参数字典,设置组件参数时,先存入字典,在构建时批量添加
internal readonly Dictionary<string, object> Parameters = new(StringComparer.Ordinal);
//构造函数
internal ComponentBuilder(RenderTreeBuilder builder)
{
this.builder = builder;
}
//添加组件参数方法,name为组件参数名称,value为组件参数值
//提供Add方法可以添加非组件定义的属性,例如html属性
public ComponentBuilder<T> Add(string name, object value)
{
Parameters[name] = value; //将参数存入字典
return this; //返回this对象,可以流式操作
}
//设置组件参数方法,selector为组件参数属性选择器表达式,value为组件参数值
//使用选择器有如下优点:
// - 当组件属性名称更改时,可自动替换
// - 通过表达式 c => c. 可以直接调出组件定义的属性,方便阅读
// - 可通过TValue直接限定属性的类型,开发时即可编译检查
public ComponentBuilder<T> Set<TValue>(Expression<Func<T, TValue>> selector, TValue value)
{
var property = TypeHelper.Property(selector); //通过属性选择器表达式获取组件参数属性
return Add(property.Name, value); //添加组件参数
}
//组件构建方法,action为返回组件对象实例的委托,默认为空不返回实例
public void Build(Action<T> action = null)
{
builder.OpenComponent<T>(0); //开始附加组件
if (Parameters.Count > 0)
builder.AddMultipleAttributes(1, Parameters); //批量添加组件参数
if (action != null)
builder.AddComponentReferenceCapture(2, value => action.Invoke((T)value)); //返回组件对象实例
builder.CloseComponent(); //结束附加组件
}
}
4. 属性选择器
为什么要用属性选择器,组件建造者类中已经提到,下面介绍如何通过属性选择器表达式来获取组件类型的属性对象。
public class TypeHelper
{
//通过属性选择器表达式来获取指定类型的属性
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. 总结
以上就是组件建造者的完整实现过程,代码不长,但这些功能足以完成手动构建Blazor组件的需求。
再学Blazor——组件建造者的更多相关文章
- Blazor组件的new使用方式与动态弹窗
1. 前言 在Blazor中的无状态组件文中,我提到了无状态组件中,有人提到这个没有diff,在渲染复杂model时,性能可能会更差.确实,这一点确实是会存在的.以上文的方式来实现无状态组件,确实只要 ...
- 再聊 Blazor,它是否值得你花时间学习
之前写了一篇文章<快速了解 ASP.NET Core Blazor>,大家关心最多的问题是,我该不该花时间去学习 Blazor.今天聊聊这个话题,并表达一下我个人的看法. 在此之前,我还是 ...
- Ant Design Blazor 组件库的路由复用多标签页介绍
最近,在 Ant Design Blazor 组件库中实现多标签页组件的呼声日益高涨.于是,我利用周末时间,结合 Blazor 内置路由组件实现了基于 Tabs 组件的 ReuseTabs 组件. 前 ...
- Blazor组件自做九: 用20行代码实现文件上传,浏览目录功能 (3)
接上篇 Blazor组件自做九: 用20行代码实现文件上传,浏览目录功能 (2) 7. 使用配置文件指定监听地址 打开 appsettings.json 文件,加入一行 "UseUrls&q ...
- Blazor组件自做一 : 使用JS隔离封装viewerjs库
Viewer.js库是一个实用的js库,用于图片浏览,放大缩小翻转幻灯片播放等实用操作 本文相关参考链接 JavaScript 模块中的 JavaScript 隔离 Viewer.js工程 Blazo ...
- Blazor组件自做二 : 使用JS隔离制作手写签名组件
Blazor组件自做二 : 使用JS隔离制作手写签名组件 本文相关参考链接 JavaScript 模块中的 JavaScript 隔离 Viewer.js工程 Blazor组件自做一 : 使用JS隔离 ...
- Blazor组件自做三 : 使用JS隔离封装ZXing扫码
Blazor组件自做三 : 使用JS隔离封装ZXing扫码 本文基础步骤参考前两篇文章 Blazor组件自做一 : 使用JS隔离封装viewerjs库 Blazor组件自做二 : 使用JS隔离制作手写 ...
- 【C】 01 - 再学C语言
“C语言还用再学吗?嵌入式工程师可是每天都在用它,大家早就烂熟于心,脱离语言这个层面了”.这样说不无道理,这门古老的语言以其简单的语法.自由的形式的而著称.使用C完成工作并不会造成太大困扰,所以很少有 ...
- [Python]再学 socket 之非阻塞 Server
再学 socket 之非阻塞 Server 本文是基于 python2.7 实现,运行于 Mac 系统下 本篇文章是上一篇初探 socket 的续集, 上一篇文章介绍了:如何建立起一个基本的 sock ...
- 再学Java 之 interface的成员变量
前言:最近在学多线程,写“哲学家就餐问题(Dining Philosophers)”的时候,需要定义一个全局的变量,即哲学家的人数.常用的做法是在其中一个类中定义一个static final的变量,然 ...
随机推荐
- 探究eFuse:硬件保障与系统安全的核心
探究eFuse:硬件保障与系统安全的核心 图1: 编程熔断的 eFuse eFUSE的全名是"Electrically Programmable Read-Only Memory Fuse& ...
- @ControllerAdvice 注解使用及原理探究
最近在新项目的开发过程中,遇到了个问题,需要将一些异常的业务流程返回给前端,需要提供给前端不同的响应码,前端再在次基础上做提示语言的国际化适配.这些异常流程涉及业务层和控制层的各个地方,如果每个地方都 ...
- json虽然简单,但这些细节你未必知道
基本介绍 JSON的全称是JavaScript Object Notation,它并不是编程语言,而是一种可以在服务器和客户端之间传输的数据格式,本来是JavaScript的子集,但现在已独立存在于各 ...
- css使用背景灵活展示雪碧图
雪碧图是把各种小图标集合在一起的png图片,通过background-position来展示雪碧图中不同位置的小图标,比如以下图片,在项目中要用到的小图标很多,如果每一个图标都作为一个png或者jpg ...
- 好用的css3特性-过渡和2D变换
css3中有很多非常好用的特性,今天来总结一下与动画相关,包括过渡.2D变换. 首先来介绍一下过渡,过渡是在进行变化的时候进行的一个缓冲,如果没有过渡,当变更了元素的位置.大小的数据时,会一瞬间完成变 ...
- 让 GPT-4 来修复 Golang “数据竞争”问题(续) - 每天5分钟玩转 GPT 编程系列(7)
目录 1. 我以为 2. 阴魂不散的"数据竞争"问题 3. 老规矩,关门放 GPT-4 3.1 复现问题 3.2 让 GPT-4 写一个单元测试 3.3 修复 Wait() 中的逻 ...
- 面霸的自我修养:synchronized专题
王有志,一个分享硬核Java技术的互金摸鱼侠 加入Java人的提桶跑路群:共同富裕的Java人 今天是<面霸的自我修养>的第3弹,内容是Java并发编程中至关重要的关键字synchroni ...
- 【双系统】Win10/Win11 引导 Ubuntu
目录 纲要 注意 写在最前 1. Win 分区 2. Ubuntu刻盘 3. 安装 Ubuntu 4. 配置引导 纲要 本文主要介绍了如何在已安装 Win10/Win11 前提下安装 Ubuntu 双 ...
- Android13深入了解 Android 小窗口模式和窗口类型
Android13深入了解 Android 小窗口模式和窗口类型 小窗模式,作为一种在移动设备上的多任务处理方式,为用户带来了便捷和高效的体验,尤其在一些特定场景下,其价值愈发凸显.以下是为什么需要小 ...
- Spring Boot虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错.内容较长,我就不翻译了,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读. 测试场景 作者采用了一个尽 ...