Blazor WASM中StateHasChanged仅触发一次问题求助
问题描述
我开发了一个基于Blazor WASM的应用,多个独立组件需要在特定事件发生时互相通信,于是创建了消息服务(MessageService)实现组件间的消息发送与订阅,多数场景下只需通知其他组件更新后重新渲染。
当前场景:
- 主页面承载多个组件
- 模态对话框组件允许用户更新Adventure对象
- 模态框完成更新后触发事件,订阅者调用
StateHasChanged()更新UI
核心问题:StateHasChanged()仅首次调用有效,后续调用无法更新UI。事件每次都会触发,StateHasChanged()也每次都执行,但UI只在第一次更新。
已尝试过设置组件属性、用async替代void等方法,均无效。
消息服务代码
public interface IMessageService { event Action<string> OnMessage; event Func<string, Task> OnMessageAsync; void SendMessage(string message); void ClearMessages(); } public class MessageService : IMessageService { public event Action<string> OnMessage; public event Func<string, Task> OnMessageAsync; public void SendMessage(string message) { OnMessage?.Invoke(message); } public void ClearMessages() { OnMessage?.Invoke(null); } }
模态对话框代码
@code { [Parameter] public entities.Adventure Adventure { get; set; } private bool isBusy = false; private DateTime startDate; private DateTime endDate; private int numberOfDays; protected override async Task OnInitializedAsync() { // 记录原始起止日期,用于检测是否被修改 startDate = Adventure.StartDate; endDate = Adventure.EndDate; numberOfDays = (endDate - startDate).Days; } private async Task OnSubmit() { try { isBusy = true; bool isDateChange = false; if (startDate != Adventure.StartDate || endDate != Adventure.EndDate) { if ((Adventure.EndDate - Adventure.StartDate).Days != numberOfDays) { await DialogService.Alert("你的行程天数与原计划不一致,修改后会丢失部分行程项(任务、活动等)。请选择与原行程天数相同的日期范围,后续可单独删除天数。", "提示", new AlertOptions() { OkButtonText = "确定" }); isBusy = false; return; } else { isDateChange = true; } } var updatedAdventure = await AdventureClient.UpdateAdventureDetails(Adventure, isDateChange); MessageService.SendMessage("AdventureUpdated"); DialogService.Close(updatedAdventure); } catch (Exception ex) { await DialogService.Alert($"保存行程时出错:{ex.Message}", "错误", new AlertOptions() { OkButtonText = "关闭" }); } finally { isBusy = false; } } }
需要重新渲染的宿主页面代码
@code { [Parameter] public Guid Id { get; set; } private entities.Adventure? adventure; protected override async Task OnInitializedAsync() { MessageService.OnMessage += MessageHandler; await GetAdventure(); } async Task GetAdventure() { try { adventure = await AdventureClient.GetAdventure(Id); } catch (Exception ex) { await DialogService.Alert($"获取行程时出错:{ex.Message}", "错误", new AlertOptions() { OkButtonText = "关闭" }); } } private void MessageHandler(string message) { if (message != null && message == "AdventureUpdated") { // 问题:仅第一次模态框更新时生效,后续更新时事件触发但UI不更新 StateHasChanged(); } } public void Dispose() { MessageService.OnMessage -= MessageHandler; } }
解决方案
1. 核心修复:更新数据源而非仅触发渲染
当前MessageHandler只调用StateHasChanged(),但页面绑定的adventure仍是旧数据,Blazor变更检测会判定数据未变化,因此UI不更新。必须同步更新数据源:
修改宿主页面的MessageHandler为异步方法,重新获取最新数据:
private async Task MessageHandler(string message) { if (message != null && message == "AdventureUpdated") { await GetAdventure(); // 显式触发渲染,确保数据更新后UI同步 await InvokeAsync(StateHasChanged); } }
2. 适配异步事件处理
原消息服务的同步事件不适合异步逻辑,修改MessageService添加异步发送方法:
public async Task SendMessageAsync(string message) { OnMessage?.Invoke(message); if (OnMessageAsync != null) { await OnMessageAsync.Invoke(message); } }
同时更新模态对话框的调用代码:
await MessageService.SendMessageAsync("AdventureUpdated");
3. 修正事件订阅逻辑
在宿主页面切换为订阅异步事件,避免上下文丢失:
protected override async Task OnInitializedAsync() { MessageService.OnMessageAsync += MessageHandler; await GetAdventure(); } public void Dispose() { MessageService.OnMessageAsync -= MessageHandler; }
4. 优化方案:直接使用模态框返回数据
模态框关闭时已返回更新后的updatedAdventure,可直接用此数据更新页面,省去API调用:
// 宿主页面中打开模态框的逻辑 private async Task OpenEditModal() { var dialogResult = await DialogService.Show<EditAdventureDialog>(adventure).Result; if (!dialogResult.Cancelled) { adventure = (entities.Adventure)dialogResult.Data; await InvokeAsync(StateHasChanged); } }
内容的提问来源于stack exchange,提问作者Don Fitz
相关产品推荐
相关产品推荐

