Blazor中foreach循环使用EventCallback点击按钮始终返回最后一项问题
结论
该现象不是Blazor框架Bug,由C#闭包捕获规则与Blazor组件参数的非标准用法共同导致。
根因说明
- 循环闭包捕获问题
你在Child.razor的foreach循环外部声明了共享变量obj,循环过程中所有迭代操作的都是这同一个变量的内存引用。所有按钮的点击事件lambda表达式捕获的都是这个共享变量本身,而非当次循环的变量值。当用户点击按钮时,循环早已执行完毕,obj存储的是最后一次赋值的rat,因此所有按钮点击都会返回列表最后一项。
这是C#语言的标准闭包行为,并非Blazor特有问题,在所有lambda捕获循环外变量的场景下都可能出现。 - 组件参数赋值不规范
Blazor框架统一管理组件[Parameter]标记的属性生命周期,父组件应当通过组件标签的属性声明传参,而非在OnAfterRenderAsync生命周期中通过@ref获取组件实例后直接修改Parameter属性。这种写法会破坏框架的参数状态同步机制,属于官方明确不推荐的反模式,虽然不是本次问题的核心触发点,但极易引发其他渲染异常。
修复步骤
1. 修复闭包捕获问题
在foreach循环内部声明局部变量存储当次迭代的条目值,让每个按钮的点击lambda捕获独立的局部变量引用,移除循环外声明的共享obj变量。
修改后的Shared/Child.razor模板部分代码:
<h3>child</h3> <p> @foreach(var item in DataSource) { // 循环内声明局部变量,存储当次迭代的独立值 var currentItem = item; <button @onclick="() => OnClickCallback(currentItem)"> @item </button> } </p>
2. 修正父组件参数传递方式
移除通过组件引用手动赋值回调的逻辑,直接在<Child>标签上绑定回调参数,符合Blazor组件参数传递规范。
修改后的Shared/Parent.razor代码:
@page "/" @inject IJSRuntime JS <PageTitle>Index</PageTitle> <h1>Hello, world!</h1> Welcome to your new app. <SurveyPrompt Title="How is Blazor working for you?" /> <!-- 直接通过标签属性绑定回调,无需@ref手动修改参数 --> <Child OnClickCallback="ShowMessage"> Bla bla bla </Child> @code{ private async Task ShowMessage(object str) { await JS.InvokeVoidAsync("alert", (string)str); } }
最佳实践优化
Blazor官方推荐组件事件回调使用EventCallback<T>类型替代Func<T, Task>:EventCallback会自动对接组件渲染上下文,事件触发后会自动通知关联组件重渲染,不需要手动调用StateHasChanged,适配性更好。
修改子组件的回调参数定义即可:
[Parameter] public EventCallback<object> OnClickCallback { get; set; }
父组件的绑定逻辑不需要做任何调整,即可正常运行。
完成上述修改后,点击任意按钮都会弹出对应按钮的文本内容,完全符合预期效果。
内容的提问来源于stack exchange,提问作者Obaigbona Joel
相关产品推荐
相关产品推荐

