Blazor组件的new使用方式与动态弹窗
1. 前言
在Blazor中的无状态组件文中,我提到了无状态组件中,有人提到这个没有diff,在渲染复杂model时,性能可能会更差。确实,这一点确实是会存在的。以上文的方式来实现无状态组件,确实只要属性发生变化,就会渲染。无状态组件是否渲染,更多的需要依靠父组件来判断。父组件不用更新,则无状态组件自然不会发生渲染。此外,有些需求,比如地图,要做的就是每次拖拽、缩放,整个地图中都要被渲染,这种纯粹用来进行数据展示的组件,使用无状态组件会更好。如果想要无状态组件不会每次都渲染,那就可以自己实现一个ShouldRender
的函数。
2. 一定要实现IComponent
接口吗?
在Blazor中的无状态组件中,我提到一个组件要想被成功被编译使用,需要满足两个条件:
- 实现
IComponent
接口 - 具有一个如下声明的虚函数:
protected virtual void BuildRenderTree(RenderTreeBuilder builder);
那,如果我们把IComponent
接口的实现声明给去掉(即仅删除: IComponent
),能够使用吗?显然不能,VS编译器都会提示你错误找不到这个组件:
RZ10012 Found markup element with unexpected name 'xx.DisplayCount'. If this is intended to be a component, add a @using directive for its namespace.
但是再想一下,vs会把所有的*.razor
文件编译为一个类,那我们不是可以直接使用这个类,new一个组件吗?这是当然是没问题的。
3. 再谈Blazor组件的渲染
在Blazor中的无状态组件中,我谈到Blazor的渲染,实际上渲染的是组件内生成的RenderFragment
DOM树。当我们创建一个*.razor
文件后,编译器会自动帮我们将组件中的DOM生成为RenderFragment
。因此无论一个*.razor
文件是否继承ComponmentBase
类,异或是是否实现IComponent
接口,只要满足上述的第二个条件——具有一个BuildRenderTree
的虚函数——就一定能够将文件内所编辑的DOM转为RenderFragment
DOM树。在之前的文章中,无状态组件StatelessComponentBase
基类的声明大致如下:
public class StatelessComponentBase : IComponent
{
private RenderFragment _renderFragment;
public StatelessComponentBase()
{
// 设置组件DOM树(的创建方式)
_renderFragment = BuildRenderTree;
}
...
}
说白了,无非是我们耍了个小聪明,利用编译器对*.razor
的编译方式,自动生成了RenderFragment
。可是,没人说_renderFragment
一定是要私有的,我们完全可以这样:
public class StatelessComponentBase
{
public RenderFragment RenderFragment { get; private set; }
public StatelessComponentBase()
{
RenderFragment = BuildRenderTree;
}
...
}
这样子,我们就可以在组件外部获取到RenderFragment
DOM树。
4. New一个组件
在3中,我们已然将组件中的RenderFragment
暴露到了外部,自然也就能够在new之后,通过实例来获取到它:
new Componment().RenderFragment
来看一个例子,是基于Counter页面修改的:
// DisplayCount组件
@inherits StatelessComponentBase
<h3>DisplayCount</h3>
<p role="status">Current count: @Count</p>
@code {
public int Count{ get; set; }
}
==================
// index页面
@page "/"
<PageTitle>Index</PageTitle>
<div>currentCount: @currentCount</div>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@renderFragment
@code {
RenderFragment? renderFragment = null;
private int currentCount = 1;
Components.DisplayCount displayCount = new Components.DisplayCount();
private void IncrementCount()
{
currentCount++;
displayCount.Count = currentCount;
}
public async override Task SetParametersAsync(ParameterView parameters)
{
displayCount.Count = currentCount;
renderFragment = displayCount.RenderFragment;
await base.SetParametersAsync(parameters);
}
}
代码运行无任何问题:
通过这种小技巧,即可实现Blazor的动态渲染。提到Blazor的动态渲染,有些人可能会讲到DynamicComponent
。DynamicComponent
通过接收组件的类型——Type,和组件的参数——Parameters,能够实现对组件进行动态渲染。这里提供了一个与DynamicComponent
不同的思路。DynamicComponent
需要将标签写在组件文件中,以实现挂载,而本文则是通过new一个组件来获取内部的RenderFragment
来进行。插个题外话,DynamicComponent
也没有继承ComponmentBase
类,和之前我提出的无状态组件的结构是相似的。
5. 动态弹窗1
想一想在使用WinForm的时候,我们只需要new和show一下,就可以打开一个窗体。现在有空动态渲染,Blazor中的弹窗组件,new and show不再是梦想。
以上述方法而言,new一个组件后,必然需要在一个组件中进行挂载。没有挂载点,Blazor组件是无法在页面上呈现出来的。一想到挂载点,我们自然会想到创建一个全局容器来实现。当我们调用show的时候,在容器组件中将RenderFragment
进行渲染即可。
Modal.razor:Modal的基类,为其添加了show和close的方法。因为Modal组件都是new出来的,需要挂载在Blazor中才能够渲染,这里通过Container中的静态属性ModalContainer来进行Modal的渲染。
@inherits StatelessComponentBase
@code {
public void Show()
{
Container.ModalContainer?.AddModal(this);
}
public void Close()
{
Container.ModalContainer?.RemoveModal(this);
}
}
Container.razor:全局容器,用于挂载Modal。
<div style="position: absolute; top:0; width: 100%; height: 100%; z-index: 9999; pointer-events: none;
display: flex; align-items:center; justify-content:center;">
@foreach(var modal in Modals)
{
<div @key="modal" style="border: 1px solid #efefef; border-radius: 4px;">
@modal.RenderFragment
</div>
}
</div>
@code {
/// <summary>
/// 由于需要在每个 new 的 Modal 中能够获取到container的实例,
/// 所以这里需要用个静态变量,在组件初始化的时候引用自身
/// </summary>
internal static Container? ModalContainer { get; private set; }
private List<Modal> Modals { get; init; } = new List<Modal>();
protected override Task OnInitializedAsync()
{
ModalContainer = this;
return base.OnInitializedAsync();
}
internal void AddModal(Modal modal)
{
if (!Modals.Contains(modal))
{
Modals.Add(modal);
StateHasChanged();
}
}
internal void RemoveModal(Modal modal)
{
if (Modals.Contains(modal))
{
Modals.Remove(modal);
StateHasChanged();
}
}
}
接下来,当我们需要添加一个Modal组件的时候,我们只需要继承Modal
即可。
// Modal1.razor
@inherits Modal
<h3>Modal1</h3>
而在使用的时候,我们可以new and show即可:
@page "/modalDemo"
@using Instantiation.Components.DyModal1
<PageTitle>Modal Demo</PageTitle>
<button class="btn btn-primary" @onclick="()=>{modal1.Show();}">OpenModal1</button>
<button class="btn btn-primary" @onclick="()=>{modal1.Close();}">CloseModal1</button>
@code {
Modal modal1 = new Modal1();
}
效果如下:
请注意最后的部分,Modal2关闭再打开后,count的计数值是没有变化的,也就是说这种方式可以保留Modal内部的状态。但是,Modal中子组件与Modal根组件(例如Modal1.razor)有些交互无法使用,例如**EventCallback**
等。
这部分的代码详见:[Pages/ModalDemo.razor](BlazorTricks/ModalDemo2.razor at main · zxyao145/BlazorTricks (github.com))。
6. 动态弹窗2
当然,使用DynamicComponent
也是可以实现的,这样你就不必使用new and show,可以直接使用泛型Add<T>()
来实现,而且可以直接使用ComponentBase
,不必将RenderFragment
暴露出来。
Container2.razor:同样需要定义一个全局的容器:
<div style="position: absolute; top:0; width: 100%; height: 100%; z-index: 9999; pointer-events: none;
display: flex; align-items:center; justify-content:center;">
@foreach(var modal in Modals)
{
<div @key="modal" style="border: 1px solid #efefef; border-radius: 4px; pointer-events: all;">
<DynamicComponent Type="modal" />
</div>
}
</div>
@code {
/// <summary>
/// 由于需要在每个 new 的 Modal 中能够获取到container的实例,
/// 所以这里需要用个静态变量,在组件初始化的时候引用自身
/// </summary>
internal static Container2? ModalContainer { get; private set; }
private List<Type> Modals { get; init; } = new List<Type>();
protected override Task OnInitializedAsync()
{
ModalContainer = this;
return base.OnInitializedAsync();
}
internal void AddModal<T>()
{
var type = typeof(T);
if (!Modals.Contains(type))
{
Modals.Add(type);
StateHasChanged();
}
}
internal void RemoveModal<T>()
{
var type = typeof(T);
if (Modals.Contains(type))
{
Modals.Remove(type);
StateHasChanged();
}
}
}
Modal3.razor:具体的弹窗Modal,注意,不必写任何继承
<h3>Modal3</h3>
@currentCount
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
StateHasChanged();
}
}
使用:
@page "/modalDemo2"
@using Instantiation.Components.DyModal2
<PageTitle>Modal Demo2</PageTitle>
<button class="btn btn-primary" @onclick="()=>{Container2.ModalContainer?.AddModal<Modal3>();}">Open DynamicComponentModalIns</button>
<button class="btn btn-primary" @onclick="()=>{Container2.ModalContainer?.RemoveModal<Modal3>();}">Close DynamicComponentModalIns</button>
效果如下:
请注意,这种方式Modal关闭再打开后,count的计数值是重新归位了0,也就是说这种方式无法保留Modal内部的状态。
这部分的代码详见:[Pages/ModalDemo2.razor](BlazorTricks/ModalDemo2.razor at main · zxyao145/BlazorTricks (github.com))。
7. 总结
本文讲述Blazor如何通过new实例化的方式进行使用,继而引起了动态弹窗的使用。动态弹窗本文写了两种方式,一是之前提到的new and show,需要较多的编码,另外一种是利用Blazor内置的组件DynamicComponent
。两种方式各有缺点。第一种可以保留内部的状态,但是Modal的跟组件与子组件的交互将有部分功能缺失(不限制于Modal的子孙组件);第二种可以可以保留组件的一切功能,但是Modal在关闭后再次打开无法保留之前内部的状态(其实这部分是可以解决的,提示:DynamicComponent
的Render
方法)。
示例代码:BlazorTricks/02-Instantiation at main · zxyao145/BlazorTricks (github.com)
Blazor组件的new使用方式与动态弹窗的更多相关文章
- Vue加载组件、动态加载组件的几种方式
https://cn.vuejs.org/v2/guide/components.html https://cn.vuejs.org/v2/guide/components-dynamic-async ...
- VUE 动态加载组件的四种方式
动态加载组件的四种方式: 1.使用import导入组件,可以获取到组件 var name = 'system'; var myComponent =() => import('../compon ...
- 微软Blazor组件发布,DevExpress v19.1.8中可用:Charts新功能
点击获取DevExpress v19.2.3最新完整版试用下载 DevExpress UI for Blazor在v19.1.8中可用,此次更新发布包括DevExpress Blazor组件的主要功能 ...
- Ant Design Blazor 组件库的路由复用多标签页介绍
最近,在 Ant Design Blazor 组件库中实现多标签页组件的呼声日益高涨.于是,我利用周末时间,结合 Blazor 内置路由组件实现了基于 Tabs 组件的 ReuseTabs 组件. 前 ...
- Blazor 组件入门指南
翻译自 Waqas Anwar 2021年3月19日的文章 <A Beginner's Guide to Blazor Components> [1] Blazor 应用程序是组件的组合, ...
- Blazor组件自做三 : 使用JS隔离封装ZXing扫码
Blazor组件自做三 : 使用JS隔离封装ZXing扫码 本文基础步骤参考前两篇文章 Blazor组件自做一 : 使用JS隔离封装viewerjs库 Blazor组件自做二 : 使用JS隔离制作手写 ...
- Android四大组件之Activity--管理方式
1. 概览 Activity的管理有静态和动态两层涵义: 静态是指Activity的代码组织结构,即Application中声明的Activity的集合,这些Activity被组织在一个APK中,有特 ...
- DevExpress Blazor组件全新来袭!增强Data Grid、TreeView API
点击获取DevExpress v19.1.7最新完整版试用下载 DevExpress UI for Blazor即将在最新的v19.1.8中可用,此次更新发布包括DevExpress Blazor组件 ...
- Blazor 组件库开发指南
翻译自 Waqas Anwar 2021年5月21日的文章 <A Developer's Guide To Blazor Component Libraries> [1] Blazor 的 ...
随机推荐
- FTP 文件传输服务
昨晚心血来潮,尝试用python写了一个ftp文件传输服务,可以接收指令,从远程ftp服务器同步指定目录数据,最后没用上,开源出来. https://github.com/jadepeng/ftp_t ...
- 最长公共子序列问题(LCS) 洛谷 P1439
题目:P1439 [模板]最长公共子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 关于LCS问题,可以通过离散化转换为LIS问题,于是就可以使用STL二分的方法O(nlogn ...
- Java中方法的定义与使用
Java中方法的定义与使用 1.方法的定义: 方法是一段可以被重复调用的代码块. 方法的声明: public static 方法返回值 方法名([参数类型 变量--]){ 方法代码体: return ...
- Linux学习 - 数值运算
1 declare 声明变量类型 declare [+/-] [选项] 变量名 - 给变量设定类型属性 + 取消变量的类型属性 -i 将变量声明为整数型 -x 将变量声明为环境变量(同export) ...
- html框架frame iframe
框架 通过使用框架,你可以在同一个浏览器窗口中显示不止一个页面.没分HTML文档称作一个框架. 缺点: 开发人员必须同时跟踪更多的HTML文档 很难打印整张页面 框架结构标签(<frameset ...
- Redis集群的三种模式
一.主从模式 通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载数据. 但是由于数据是存储在一台服务器上的, ...
- mysql读写分离(proxySQL) lamp+proxysql+nfs
先在主从节点安装mysql [root@master-mariadb ~]# yum install mariadb-server -y [root@slave-mariadb ~]# yum ins ...
- promise ,async 小记
Promise Promise 对象用于表示一个异步操作的最终状态(完成或失败),以及该异步操作的结果值. 摇色子游戏,随机1-6的一个整数,并且将其返回. function fn() { retur ...
- InnoDB学习(四)之RedoLog和UndoLog
BinLog是MySQL Server层的日志,所有的MySQL存储引擎都支持BinLog.BinLog可以支持主从复制和数据恢复,但是对事务的ACID特性支持比较差.InnoDB存储引擎引入Redo ...
- [BUUCTF]REVERSE——[SUCTF2019]SignIn
[SUCTF2019]SignIn 附件 步骤: 无壳,64位ida载入 程序调用了 __gmpz_init_set_str 函数,这是一个 GNU 高精度算法库,在RSA加密中见过几次,加上6553 ...