使用 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——组件建造者的更多相关文章

  1. Blazor组件的new使用方式与动态弹窗

    1. 前言 在Blazor中的无状态组件文中,我提到了无状态组件中,有人提到这个没有diff,在渲染复杂model时,性能可能会更差.确实,这一点确实是会存在的.以上文的方式来实现无状态组件,确实只要 ...

  2. 再聊 Blazor,它是否值得你花时间学习

    之前写了一篇文章<快速了解 ASP.NET Core Blazor>,大家关心最多的问题是,我该不该花时间去学习 Blazor.今天聊聊这个话题,并表达一下我个人的看法. 在此之前,我还是 ...

  3. Ant Design Blazor 组件库的路由复用多标签页介绍

    最近,在 Ant Design Blazor 组件库中实现多标签页组件的呼声日益高涨.于是,我利用周末时间,结合 Blazor 内置路由组件实现了基于 Tabs 组件的 ReuseTabs 组件. 前 ...

  4. Blazor组件自做九: 用20行代码实现文件上传,浏览目录功能 (3)

    接上篇 Blazor组件自做九: 用20行代码实现文件上传,浏览目录功能 (2) 7. 使用配置文件指定监听地址 打开 appsettings.json 文件,加入一行 "UseUrls&q ...

  5. Blazor组件自做一 : 使用JS隔离封装viewerjs库

    Viewer.js库是一个实用的js库,用于图片浏览,放大缩小翻转幻灯片播放等实用操作 本文相关参考链接 JavaScript 模块中的 JavaScript 隔离 Viewer.js工程 Blazo ...

  6. Blazor组件自做二 : 使用JS隔离制作手写签名组件

    Blazor组件自做二 : 使用JS隔离制作手写签名组件 本文相关参考链接 JavaScript 模块中的 JavaScript 隔离 Viewer.js工程 Blazor组件自做一 : 使用JS隔离 ...

  7. Blazor组件自做三 : 使用JS隔离封装ZXing扫码

    Blazor组件自做三 : 使用JS隔离封装ZXing扫码 本文基础步骤参考前两篇文章 Blazor组件自做一 : 使用JS隔离封装viewerjs库 Blazor组件自做二 : 使用JS隔离制作手写 ...

  8. 【C】 01 - 再学C语言

    “C语言还用再学吗?嵌入式工程师可是每天都在用它,大家早就烂熟于心,脱离语言这个层面了”.这样说不无道理,这门古老的语言以其简单的语法.自由的形式的而著称.使用C完成工作并不会造成太大困扰,所以很少有 ...

  9. [Python]再学 socket 之非阻塞 Server

    再学 socket 之非阻塞 Server 本文是基于 python2.7 实现,运行于 Mac 系统下 本篇文章是上一篇初探 socket 的续集, 上一篇文章介绍了:如何建立起一个基本的 sock ...

  10. 再学Java 之 interface的成员变量

    前言:最近在学多线程,写“哲学家就餐问题(Dining Philosophers)”的时候,需要定义一个全局的变量,即哲学家的人数.常用的做法是在其中一个类中定义一个static final的变量,然 ...

随机推荐

  1. word中查找替换不能使用 解决方案

    打开查找,然后点更多,最下面点不限定格式

  2. SQLite入门指南:轻松学习带有实例的完整教程(含示例)

    SQLite官网:https://www.sqlite.org/index.html 源视频教程:https://www.bilibili.com/video/BV1Zz411i78o 菜鸟教程文档: ...

  3. nacos系列:简介和安装

    目录 版本选择 安装 windows安装 centos安装 mysql方式存储 官网:https://nacos.io github:https://github.com/alibaba/nacos ...

  4. 使用synapse搭建matrix去中心化加密通信服务

    前言 首先必须介绍下Matrix.Matrix是一个开源.可交互.去中心化的实时通信服务框架.使用Matrix可以搭建安全的通信服务器,配合支持 Matrix 的客户端可以实现个人.团队间的实时聊天交 ...

  5. 通过替换dll实现后门功能的恶意代码

    通过替换Kernel32.dll来实现的后门功能的恶意代码. 该恶意代码存在一个exe可执行文件和一个dll动态链接库,需要分别进行分析 一.待解决问题 这个恶意代码执行了什么功能? 通过什么方式实现 ...

  6. 接到一个新需求应该怎么做?(V1.0)

    接到一个新需求应该怎么做?(V1.0) 1 背景 在做业务研发的时候,经常会接到一些 产品需求/技术需求, 无论需求大小,都需要一套可以重复使用的方法论,来保证整个项目的正常交付,这篇思考就是总结梳理 ...

  7. RocketMQ 系列(二) 环境搭建

    RocketMQ 系列(二) 环境搭建 上一个章节对于 RocketMQ 作了一些概念上的介绍,如果你对于 RocketMQ 没有概念,不妨先看RocketMQ系列(一) 基本介绍. 这个章节主要介绍 ...

  8. API接口获取快手商品详情(封装代码)

    快手是中国最大的短视频平台之一,也是许多电商企业进行推广的重要渠道.为了更好地了解快手的商品信息,我们可以通过API接口来获取商品详情. 首先,我们需要了解快手API接口和相应的文档 接下来,我们需要 ...

  9. Python 导入 Excel多sheet

    Python 导入 Excel多sheet 假设表格的样式如下 import os import sys import django BASE_DIR = os.path.dirname(os.pat ...

  10. @RequiredArgsConstructor和@Authwired

    我们在java后端书写接口时,对service层成员变量的注入和使用有以下两种实现方式: 1) @RequiredArgsConstructor import lombok.RequiredArgsC ...