You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor提示组件首次显示后无法再次展示问题求助

Blazor Toaster组件客户端验证重复触发失效问题排查

我开发了一个Toaster提示组件,用于展示错误与成功提示:

  • 服务端往返请求场景下,组件可正常单次显示提示
  • 客户端验证场景下,首次触发错误可正常显示,后续再触发错误时组件不再展示
  • 成功提示3秒后自动消失,失败提示需用户手动关闭
  • 已多次添加StateHasChanged()但无效果,组件的OnParametersSetAsync方法首次显示后不再触发
  • 手动关闭提示后,再次触发错误逻辑,浏览器中组件仍保留hide类

组件代码

Razor视图

<div class="toast @toastClass" role="alert" aria-live="assertive" aria-atomic="true">
  <div class="toast-header">
      <strong class="me-auto">@toastHeader</strong>
      <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close" @onclick="()=>ToastClose()"></button>
  </div>
  <div class="toast-body">
      @toastMessage
  </div>
</div>

后置C#类

public partial class Toaster
{
private System.Timers.Timer timer;

[Parameter]
public string toastClass { get; set; } = "hide";

[Parameter]
public string toastHeader { get; set; } = "Success";
[Parameter]
public string toastMessage { get; set; } = "Succesfully saved!";

protected override async Task OnParametersSetAsync()
{
    if (toastClass != "hide")
        StartTimer(3000);
}

public void StartTimer(double interval)
{
    timer = new System.Timers.Timer(interval);
    timer.Elapsed += TimerElapsedHandler;
    timer.Start();
}

private void TimerElapsedHandler(object? sender, ElapsedEventArgs e)
{
    timer.Stop();
    timer.Dispose();
    if (toastClass != "show toast-error")
        ToastClose();
  //  StateHasChanged();
}

protected void ToastClose()
{
    toastClass = "hide";
}
}

页面调用与验证逻辑

组件调用代码

<Toaster toastHeader=@toastHeader toastMessage=@toastMessage toastClass=@toastClass></Toaster>

客户端验证逻辑

......
if (RemainingCartons != 0) 
{
    errMsg.AppendLine("Remaining cartons are not allocated correctly.");
}

if (RemainingUnits != 0)
{
    errMsg.AppendLine("Remaining units are not allocated correctly.");
}
if (RemainingGOHSets != 0)
{
    errMsg.AppendLine("Remaining GOH Sets are not allocated correctly.");
}

if (RemainingGOHUnits != 0)
{
    errMsg.AppendLine("Remaining GOH Units are not allocated correctly.");
}

if (errMsg.ToString() != string.Empty && errMsg != null)
{
    toastHeader = "Error";
    toastMessage = errMsg.ToString();
    toastClass = "show toast-error";
    
    return false;
}....

问题根源分析

  1. 子组件直接修改Parameter破坏单向数据流
    toastClass是父组件传递的参数,子组件的ToastClose方法直接将其改为hide,导致父组件后续更新toastClass为show toast-error时,子组件本地值与父组件不一致,Blazor不会触发OnParametersSetAsync更新。

  2. 定时器回调未触发UI更新
    TimerElapsedHandler运行在非UI线程,即使修改了toastClass,也没有调用StateHasChanged()通知Blazor渲染UI。

  3. 客户端验证未强制触发父组件状态更新
    验证逻辑中修改父组件属性后,没有调用StateHasChanged(),Blazor可能不会立即同步更新子组件参数,尤其是连续触发时状态变更会被合并。

解决方案

1. 改用EventCallback实现双向绑定

子组件不直接修改参数,通过事件通知父组件更新:

public partial class Toaster
{
private System.Timers.Timer timer;

[Parameter]
public string ToastClass { get; set; } = "hide";
[Parameter]
public EventCallback<string> ToastClassChanged { get; set; }

[Parameter]
public string ToastHeader { get; set; } = "Success";
[Parameter]
public string ToastMessage { get; set; } = "Succesfully saved!";

protected override async Task OnParametersSetAsync()
{
    if (ToastClass != "hide")
        StartTimer(3000);
}

public void StartTimer(double interval)
{
    timer?.Dispose(); // 清理旧定时器
    timer = new System.Timers.Timer(interval);
    timer.Elapsed += TimerElapsedHandler;
    timer.Start();
}

private async void TimerElapsedHandler(object? sender, ElapsedEventArgs e)
{
    timer.Stop();
    timer.Dispose();
    if (ToastClass != "show toast-error")
    {
        await InvokeAsync(() => ToastClassChanged.InvokeAsync("hide"));
    }
}

protected async Task CloseToast()
{
    await ToastClassChanged.InvokeAsync("hide");
}
}

父组件调用时使用双向绑定:

<Toaster ToastHeader=@toastHeader ToastMessage=@toastMessage @bind-ToastClass=@toastClass></Toaster>

2. 验证后强制触发父组件更新

在验证逻辑修改属性后添加状态更新:

if (errMsg.ToString() != string.Empty && errMsg != null)
{
    toastHeader = "Error";
    toastMessage = errMsg.ToString();
    toastClass = "show toast-error";
    StateHasChanged(); // 强制触发组件渲染
    return false;
}

3. 定时器回调切换到UI线程

用InvokeAsync包装定时器内的UI操作,确保在Blazor的UI上下文执行:

private async void TimerElapsedHandler(object? sender, ElapsedEventArgs e)
{
    timer.Stop();
    timer.Dispose();
    if (ToastClass != "show toast-error")
    {
        await InvokeAsync(() => ToastClassChanged.InvokeAsync("hide"));
    }
}

内容的提问来源于stack exchange,提问作者bilpor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 19:50:16