再学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的变量,然 ...
随机推荐
- rman catalog 遇到的一个错误
[oracle@source admin]$ sqlplus / as sysdba SQL*Plus: Release 11.2.0.3.0 Production on Thu Jun 22 09: ...
- MySQL8_SQL语法
MySQL8_SQL语法 SQL 全称 Structured Query Language,结构化查询语言.操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准 . 一.SQL通用语法 在学 ...
- 如何通过cookie、session鉴权(nodejs/koa)
http是一种无状态的协议,每一个请求都是独立的,即使同一个页面向服务器发送多次请求,服务器也无法区分是不是同一用户,所以这个时候可以借助于cookie来做身份认证,当用户登录成功,服务器为浏览器设置 ...
- [k8s]使用nfs挂载pod的应用日志文件
前言 某些特殊场景下应用日志无法通过elk.grafana等工具直接查看,需要将日志文件挂载出来再处理.本文以nfs作为远程存储,统一存放pod日志. 系统版本:CentOS 7 x86-64 宿主机 ...
- Jmeter线程组间传递变量
做接口测试,上一个线程组(A线程组)提取的变量,需要传递给下一个线程组(B线程组)使用.故需要将A线程组内提取的变量设置为全局变量.实现如下: 1. json提取变量(A线程组) 通过json提取器, ...
- Win10安装Oracle-21C
1.前期工作 下载安装包:OracleXE213_Win64.zip 解压安装包 2.开始安装 注意:以管理员身份运行 ++++++++++++++++++++++分割线+++++++++++++++ ...
- ChatGPT赋能低代码开发:打造智能应用的双重引擎
摘要:本文摘自葡萄城低代码产品活字格的资深用户(格友超哥)所撰写的文章:<惊叹表现!活字格+ChatGPT:低代码开发智能应用的巨大潜力>. ChatGPT的functions函数使用方 ...
- 组合查询(left_inner_right)与排序(order by _DESC _ASC)在题目中的应用
1,想要让哪一列放在开头或者结尾,只需要将select中的查询位置放在最开始或者结尾即可: 2,组合查询要注意使用 on 加上组合条件: 3,order by 默认升序(ASC),降序使用:order ...
- P1113 杂务 (DAG拓扑排序--DP)
这是一道拓扑排序的模板题 0 额. 所需的前置知识: 图论相关的基本概念 建图,存图 图的遍历 非常入门的DP 下面进入正文 1 引入 拓扑排序是一类用于处理 DAG(Directed acyclic ...
- 《Kali渗透基础》13. 无线渗透(三)
@ 目录 1:无线通信过程 1.1:Open 认证 1.2:PSK 认证 1.3:关联请求 2:加密 2.1:Open 无加密网络 2.2:WEP 加密系统 2.3:WPA 安全系统 2.3.1:WP ...