Blazor组件的new使⽤⽅式与动态弹窗
1. 前⾔
在⽂中,我提到了⽆状态组件中,有⼈提到这个没有diff,在渲染复杂model时,性能可能会更差。确实,这⼀点确实是会存在的。以上⽂的⽅式来实现⽆状态组件,确实只要属性发⽣变化,就会渲染。⽆状态组件是否渲染,更多的需要依靠⽗组件来判断。⽗组件不⽤更新,则⽆状态组件⾃然不会发⽣渲染。此外,有些需求,⽐如地图,要做的就是每次拖拽、缩放,整个地图中都要被渲染,这种纯粹⽤来进⾏数据展⽰的组件,使⽤⽆状态组件会更好。如果想要⽆状态组件不会每次都渲染,那就可以⾃⼰实现⼀个ShouldRender的函数。
2. ⼀定要实现IComponent接⼝吗?
在中,我提到⼀个组件要想被成功被编译使⽤,需要满⾜两个条件:
1. 实现IComponent接⼝
2. 具有⼀个如下声明的虚函数: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的渲染,实际上渲染的是组件内⽣成的RenderFragmentDOM树。当我们创建⼀个*.razor⽂件后,编译器会⾃动帮我们将组件中的DOM⽣成为RenderFragment。因此⽆论⼀个*.razor⽂件是否继承ComponmentBase类,异或是是否实现IComponent接⼝,只要满⾜上述的第⼆个条件——具有⼀个BuildRenderTree的虚函数——就⼀定能够将⽂件内所编辑的DOM转为RenderFragmentDOM树。在之前的⽂章中,⽆状态组
件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;
}
...
}
这样⼦,我们就可以在组件外部获取到RenderFragmentDOM树。
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,和组件的参数——Paramet
ers,能够实现对组件进⾏动态渲染。这⾥提供了⼀个与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" >
@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]()。
6. 动态弹窗2
当然,使⽤DynamicComponent也是可以实现的,这样你就不必使⽤new and show,可以直接使⽤泛型Add<T>()来实现,⽽且可以直接使
⽤ComponentBase,不必将RenderFragment暴露出来。
Container2.razor:同样需要定义⼀个全局的容器:
<div >
@foreach(var modal in Modals)
{
<div @key="modal" >
<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内部的状态。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。