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

Blazor组件StateHasChanged触发后OnInitialized重复执行问题排查

Blazor组件StateHasChanged后重复触发OnInitialized的原因分析

我们开发Blazor时碰到一个异常:名为View的组件,在根组件调用StateHasChanged后会被重新创建,导致OnInitialized方法重复执行。按照Blazor的设计,这个方法本该只在组件首次创建时执行一次,而且View组件已经存储在栈类型的私有字段_navStack中。

有问题的根组件Razor代码

@foreach (var rec in _navStack)
{
    <div class="viewport" style="height : calc(100vh - 104px)">
        <div @key="rec.Id" class="h-100 @(rec == _navStack.Peek() ? "d-block" : "d-none")">
            @rec.Fragment
        </div>
    </div>
}

问题场景

用户点击按钮触发根组件调用StateHasChanged后,foreach代码块执行,渲染rec.Fragment(即View组件)时,其OnInitialized方法被再次执行。

修复后的代码

把viewport容器移到foreach外部后,问题消失:

<div class="viewport" style="height : calc(100vh - 104px)">
@foreach (var rec in _navStack)
{
    <div @key="rec.Id" class="h-100 @(rec == _navStack.Peek() ? "d-block" : "d-none")">
        @rec.Fragment
    </div>
}
</div>

相关CSS样式

.viewport {
    min-height: 100%;
    padding: 0 10px;
    overflow: clip;
}
.d-block {
  display: block !important;
}
.d-none {
  display: none !important;
}

问题原因

核心在于Blazor的虚拟DOM diff算法的节点上下文识别逻辑:

  • 当viewport div放在foreach内部时,每个循环项都会生成一个独立的父容器。Blazor对比虚拟DOM时,会将每个循环项的父元素视为不同的节点上下文。
  • 虽然给内部包裹Fragment的div加了@key="rec.Id",但这个key是相对于它的直接父节点(foreach里的viewport div)生效的。根组件触发重渲染时,Blazor会重新遍历_navStack,为每个rec重新创建viewport div的虚拟节点,此时Fragment所在的div即便有key,也因为父节点上下文改变,被Blazor判定为新的组件节点,从而销毁旧的View实例并重建,导致OnInitialized再次执行。
  • 把viewport移到foreach外部后,所有循环项的div都处于同一个父节点下,@key="rec.Id"能正确让Blazor识别每个循环项的唯一性。重渲染时,Blazor只会对比现有组件实例的状态,不会销毁重建,自然不会重复触发OnInitialized。

另外要注意:CSS的d-none只是控制元素是否显示,不会从DOM树中移除组件实例,所以移到外部后,所有View实例都保留在同一个父容器的上下文里,diff算法可以正确复用这些实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 19:32:16