Blazor中Select2标签控件重复实例事件未触发问题求助
Blazor Select2标签控件事件触发异常问题
问题概述
基于Select2封装了Blazor标签控件,效果如下:
但新增标签时事件触发不符合预期。
预期行为
- 将「订单编号」设置为
O1234 - 组件的
SelectedValuesChanged事件触发,传入O1234 - 将「客户订单编号」设置为
CO1234 - 组件的
SelectedValuesChanged事件触发,传入CO1234
实际行为
- 将「订单编号」设置为
O1234 - 组件的
SelectedValuesChanged事件触发,传入O1234 - 将「客户订单编号」设置为
CO1234 - 无事件触发
组件代码
C#组件代码(Select2TagSelect.razor)
@inject IJSRuntime JSRuntime @namespace BillOfLading.UI.Web.Components.Shared @using Microsoft.JSInterop <select id="@ElementId" class="form-control select2" multiple="multiple"></select> @code { [Parameter] public string ElementId { get; set; } = "select2_" + Guid.NewGuid().ToString("N"); [Parameter] public IList<string> SelectedValues { get; set; } = new List<string>(); [Parameter] public EventCallback<IList<string>> SelectedValuesChanged { get; set; } private DotNetObjectReference<Select2TagSelect>? objRef; protected override void OnInitialized() { objRef = DotNetObjectReference.Create(this); } private async Task OnSelectionChanged(ChangeEventArgs args) { SelectedValues = await JSRuntime.InvokeAsync<IList<string>>("select2InteropTag.getSelectedValues", ElementId); await SelectedValuesChanged.InvokeAsync(SelectedValues); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await JSRuntime.InvokeVoidAsync("select2InteropTag.initializeSelect2Tag", objRef, ElementId); } } [JSInvokable] public async void SetSelectedText(IList<string> selected) { SelectedValues = selected; await SelectedValuesChanged.InvokeAsync(SelectedValues); } }
JavaScript互调用代码
window.select2InteropTag = { initializeSelect2Tag: function (dotNetHelper, elementId) { $('#' + elementId).select2({ tags: true, tokenSeparators: [',', ' '] }).on('change', (event) => { console.log("Element " + elementId + " has changed"); var data = $('#' + elementId).select2('data'); if (data) { var value = data.map((element) => element.text); dotNetHelper.invokeMethodAsync('SetSelectedText', value); } }); } };
页面渲染代码
<div class="row"> <div class="mb-2 col-md-6" style="margin-bottom: 0px;"> <label class="form-label">订单编号</label> <Select2TagSelect ElementId="@($"select2-orders-{ShortGuidGenerator.ShortGuid()}")" SelectedValuesChanged="ChangeOrders"></Select2TagSelect> </div> <div class="mb-2 col-md-6" style="margin-bottom: 0px;"> <label class="form-label">客户订单编号</label> <Select2TagSelect ElementId="@($"select2-customerOrders-{ShortGuidGenerator.ShortGuid()}")" SelectedValuesChanged="ChangeCustomerOrders"></Select2TagSelect> </div> </div> //... 省略部分代码 ... @code { [Parameter] public BillPropertiesViewModel Model { get; set; } [Parameter] public EventCallback<BillPropertiesViewModel> ModelChanged { get; set; } private void ChangeOrders(IList<string> obj) { Model.Orders = obj; ModelChanged.InvokeAsync(Model); } private void ChangeCustomerOrders(IList<string> obj) { Model.CustomerOrders = obj; ModelChanged.InvokeAsync(Model); } }
异常现象
JavaScript控制台显示两个控件的change事件均已触发(例如输出"Element xyz has changed"),但第二个控件的JS与Blazor C#互调用未生效。
已尝试方案
移除OnAfterRenderAsync中的firstRender判断后,两个组件都能触发事件,但会导致事件重复注册(每次组件重渲染都会注册JS事件),存在性能隐患,不希望采用该方案。
问题原因及解决方案
原因分析
- 异步方法类型错误:
SetSelectedText使用async void类型,Blazor中异步void方法会脱离组件同步上下文,容易导致调用失效或异常无法捕获。 - DotNetObjectReference生命周期管理缺失:未正确释放
DotNetObjectReference,当父组件重渲染时,旧组件实例的引用可能被GC回收,导致JS调用C#方法时引用失效。 - 动态ElementId导致实例频繁重建:父组件重渲染时,
ShortGuidGenerator.ShortGuid()会生成新的ElementId,旧组件实例被销毁,新实例的JS事件注册可能存在上下文衔接问题。
解决方案
1. 修复异步方法类型
将SetSelectedText的返回类型从async void改为async Task,确保Blazor能正确处理异步上下文:
[JSInvokable] public async Task SetSelectedText(IList<string> selected) { SelectedValues = selected; await SelectedValuesChanged.InvokeAsync(SelectedValues); }
2. 实现IAsyncDisposable管理引用生命周期
添加资源释放逻辑,避免无效引用和内存泄漏:
@implements IAsyncDisposable // ... 其他代码 ... public async ValueTask DisposeAsync() { if (objRef != null) { objRef.Dispose(); // 销毁Select2实例,清理DOM绑定 await JSRuntime.InvokeVoidAsync("select2InteropTag.destroySelect2", ElementId); } }
对应的JS补充销毁方法:
window.select2InteropTag = { // ... 原有初始化代码 ... destroySelect2: function(elementId) { const $el = $('#' + elementId); if ($el.data('select2')) { $el.select2('destroy'); } } };
3. 优化ElementId生成逻辑
避免父组件重渲染时频繁生成新的ElementId,可基于组件的稳定标识(如绑定数据的唯一ID)生成:
<Select2TagSelect ElementId="@($"select2-orders-{Model.Id}")" SelectedValuesChanged="ChangeOrders"></Select2TagSelect>
4. JS调用添加错误捕获
在invokeMethodAsync后添加错误处理,便于排查问题:
dotNetHelper.invokeMethodAsync('SetSelectedText', value) .catch(err => console.error("C#方法调用失败:", err));
关键修复总结
- 异步JS调用的C#方法必须使用
async Task返回类型,禁止async void - 实现
IAsyncDisposable正确释放DotNetObjectReference和Select2实例 - 避免无意义的动态ElementId生成,保持组件实例的稳定性
- 添加错误捕获,快速定位互调用失败原因
内容的提问来源于stack exchange,提问作者Killnine
相关产品推荐
相关产品推荐

