如何创建Blazor组件实例并传递至其他组件渲染且保留事件绑定?
问题描述
实现了一个工厂类用于创建继承自ChildBase的各类组件实例,尝试将这些派生类实例传递给父组件渲染时,通过RenderFragment实现了初始渲染,但Blazor会再次调用派生类的构造函数,导致页面显示的是新实例,丢失了原有的事件处理器和构造参数,无法响应交互。
原代码
ChildBase.cs
public class ChildBase : ComponentBase { public EventHandler? OnClick = null; }
Child1.razor
@inherits ChildBase <div style="width:100px; height: 200px; background:red;"> <button @onClick=OnClick>Click Child 1</button> </div> @code{ private async Task OnClick() { // do something if(base.OnClick != null) { base.OnClick(this, EventArgs.Empty); } } }
Child2.razor
@inherits ChildBase <div style="width:200px; height: 100px; background:yellow;"> <button @onClick=OnClick>Click Child 2</button> </div> @code{ private async Task OnClick() { // do something other if(base.OnClick != null) { base.OnClick(this, EventArgs.Empty); } } }
Page1.razor
<button @onClick="ShowChild1">One</button> <button @onClick="ShowChild2">Two</button> <br/> <br/> @if(ContentFragment != null) { @ContentFragment } @code{ private RenderFragment? ContentFragment; private void ShowChild1() { ChildBase child = new Child1(); child.OnClick += OnChildClicked; ContentFragment = createRenderFragment(child); } private void ShowChild2() { ChildBase child = new Child2(); child.OnClick += OnChildClicked; ContentFragment = createRenderFragment(child); } private void OnChildClicked(object? sender, EventArgs e) { //Do something } private RenderFragment createRenderFragment(ChildBase child) => builder => { builder.OpenComponent(0, child.GetType()); builder.CloseComponent(); }; }
解决方案
Blazor的组件渲染系统会自主管理组件的生命周期,包括实例的创建、初始化和销毁。手动new出来的组件实例不会被Blazor的渲染上下文跟踪,当组件重新渲染时,Blazor会根据RenderFragment中的指令重新创建组件实例,这就是构造函数被调用两次、原有事件绑定丢失的原因。
要解决这个问题,不需要手动创建组件实例,而是通过传递组件类型和参数(包括事件回调)让Blazor负责实例创建,同时保留状态和事件绑定:
修改步骤
- 将
ChildBase中的OnClick字段改为可通过组件参数设置的属性(或改用Blazor推荐的EventCallback); - 在父组件中保存目标组件类型和参数,不再手动创建实例;
- 更新
RenderFragment的创建逻辑,通过AddAttribute方法传递事件回调。
修改后的代码
ChildBase.cs(可选:改用EventCallback更符合Blazor规范)
public class ChildBase : ComponentBase { // 兼容原有EventHandler public EventHandler? OnClick { get; set; } // 推荐使用EventCallback实现Blazor组件交互 [Parameter] public EventCallback<MouseEventArgs>? OnClickCallback { get; set; } }
Child1.razor(更新点击逻辑适配参数)
@inherits ChildBase <div style="width:100px; height: 200px; background:red;"> <button @onClick=HandleClick>Click Child 1</button> </div> @code{ private async Task HandleClick(MouseEventArgs e) { // 执行自身逻辑 // do something // 触发原有EventHandler OnClick?.Invoke(this, EventArgs.Empty); // 触发EventCallback(若使用) if (OnClickCallback.HasDelegate) { await OnClickCallback.InvokeAsync(e); } } }
Child2.razor(同理更新)
@inherits ChildBase <div style="width:200px; height: 100px; background:yellow;"> <button @onClick=HandleClick>Click Child 2</button> </div> @code{ private async Task HandleClick(MouseEventArgs e) { // 执行自身逻辑 // do something other // 触发原有EventHandler OnClick?.Invoke(this, EventArgs.Empty); // 触发EventCallback(若使用) if (OnClickCallback.HasDelegate) { await OnClickCallback.InvokeAsync(e); } } }
Page1.razor(核心修改:不再手动创建实例)
<button @onClick="ShowChild1">One</button> <button @onClick="ShowChild2">Two</button> <br/> <br/> @if (CurrentComponentType != null) { @RenderCurrentComponent() } @code{ private Type? CurrentComponentType; private void ShowChild1() { CurrentComponentType = typeof(Child1); } private void ShowChild2() { CurrentComponentType = typeof(Child2); } private void OnChildClicked(object? sender, EventArgs e) { // 处理子组件点击事件 Console.WriteLine($"Child clicked: {sender?.GetType().Name}"); } private RenderFragment RenderCurrentComponent() => builder => { if (CurrentComponentType == null) return; // 打开组件并传递参数 builder.OpenComponent(0, CurrentComponentType); // 传递EventHandler类型的事件绑定 builder.AddAttribute(1, nameof(ChildBase.OnClick), EventCallback.Factory.Create<EventArgs>(this, OnChildClicked)); // 若使用EventCallback,传递对应参数 // builder.AddAttribute(2, nameof(ChildBase.OnClickCallback), EventCallback.Factory.Create<MouseEventArgs>(this, HandleChildClickCallback)); builder.CloseComponent(); }; // 若使用EventCallback的回调方法 // private async Task HandleChildClickCallback(MouseEventArgs e) // { // Console.WriteLine("Child clicked via EventCallback"); // } }
关键说明
- Blazor会根据
RenderCurrentComponent中的指令创建组件实例,并将参数(包括事件绑定)注入到组件中,确保状态和事件回调被正确跟踪; - 避免手动创建组件实例,让Blazor渲染系统负责实例管理,这是符合Blazor组件生命周期设计的正确做法;
- 若需传递更多构造参数或状态,可通过组件参数、依赖注入或状态容器实现。
内容的提问来源于stack exchange,提问作者G You
相关产品推荐
相关产品推荐

