如何判断所有子组件的OnInitializedAsync()方法已执行完成?
解决Blazor仪表板全局加载遮罩的优雅方案
针对你遇到的「权限控制导致子组件数量不固定,不想硬编码计数」的问题,这里有个不用挨个组件加事件的实现方式:
核心思路
让每个需要跟踪加载状态的子组件主动向父组件报告自己的加载状态,父组件只统计当前处于加载中的组件数量,等计数归0就自动隐藏遮罩——完全不用管具体有多少个组件,权限控制带来的组件增减会自动适配。
1. 父组件添加加载跟踪逻辑
在仪表板的代码块里维护一个计数器,提供两个方法供子组件调用:
// Dashboard.razor @code 块或后台代码文件 private int _loadingComponentCount; // 只要还有组件在加载,就显示遮罩 private bool _isLoading => _loadingComponentCount > 0; // 子组件开始加载时调用 public void TrackComponentStart() { _loadingComponentCount++; StateHasChanged(); } // 子组件加载完成时调用 public void TrackComponentEnd() { if (_loadingComponentCount > 0) { _loadingComponentCount--; StateHasChanged(); } }
2. 封装可复用的子组件基类
为了避免每个子组件重复写状态报告代码,创建一个基类让需要跟踪的子组件继承:
// LoadTrackedBase.cs public class LoadTrackedBase : ComponentBase { // 通过级联参数获取父组件实例 [CascadingParameter] public Dashboard ParentDashboard { get; set; } = default!; protected override async Task OnInitializedAsync() { // 初始化开始时通知父组件 ParentDashboard.TrackComponentStart(); try { // 执行子组件自身的初始化逻辑 await base.OnInitializedAsync(); } finally { // 无论成功失败,都通知父组件加载完成 ParentDashboard.TrackComponentEnd(); } } }
3. 改造现有子组件
所有需要跟踪加载状态的子组件,只需要继承上面的基类即可,原有业务逻辑完全不用改:
// SalesChart.razor @inherits LoadTrackedBase <div>销售图表展示区域</div> @code { protected override async Task OnInitializedAsync() { // 原有的初始化逻辑,比如调用API获取数据 await FetchSalesData(); } private async Task FetchSalesData() { // 模拟API请求延迟 await Task.Delay(1500); } }
4. 父组件添加全局加载遮罩
在Dashboard.razor中添加遮罩层,用_isLoading控制显示:
@page "/dashboard" <!-- 全局加载遮罩 --> @if (_isLoading) { <div style="position: fixed; top:0; left:0; right:0; bottom:0; background: rgba(0,0,0,0.5); display:flex; align-items:center; justify-content:center;"> <div style="width:50px; height:50px; border:4px solid #fff; border-top-color:#2196F3; border-radius:50%; animation: spin 1s linear infinite;"></div> </div> } <style> @keyframes spin { to { transform: rotate(360deg); } } </style> <div class="dashboard-content"> <!-- 权限控制的子组件 --> @if (IsAdmin) { <AdminAnalytics /> } <SalesChart /> <RecentOrders /> <CustomerSummary /> <!-- 其他子组件 --> </div> @code { [Inject] private AuthenticationStateProvider AuthState { get; set; } = default!; private bool IsAdmin { get; set; } protected override async Task OnInitializedAsync() { // 你原有的3行初始化代码,比如获取用户权限 var authState = await AuthState.GetAuthenticationStateAsync(); IsAdmin = authState.User.IsInRole("Admin"); // 其他初始化逻辑 } // 前面的加载跟踪代码 private int _loadingComponentCount; private bool _isLoading => _loadingComponentCount > 0; public void TrackComponentStart() { _loadingComponentCount++; StateHasChanged(); } public void TrackComponentEnd() { if (_loadingComponentCount > 0) { _loadingComponentCount--; StateHasChanged(); } } }
5. 适配bUnit测试
如果要在测试中用WaitFor()等待页面加载完成,直接监听父组件的_isLoading状态即可:
[Test] public async Task Dashboard_Loads_All_Admin_Components() { using var ctx = new TestContext(); // 模拟管理员身份 ctx.Services.AddSingleton<AuthenticationStateProvider>(new MockAdminAuthState()); var dashboard = ctx.RenderComponent<Dashboard>(); // 等待所有组件加载完成 await dashboard.WaitForState(() => !dashboard.Instance._isLoading); // 断言权限内的组件已渲染 dashboard.FindComponent<AdminAnalytics>(); dashboard.FindComponent<SalesChart>(); }
额外优化:全局加载服务(多页面复用)
如果多个页面都需要这种加载跟踪功能,可以把计数器逻辑封装成全局服务,通过依赖注入使用,原理和上面一致,只是把状态放在服务中,父组件监听服务的状态变化即可,避免重复代码。
内容的提问来源于stack exchange,提问作者David Thielen
相关产品推荐
相关产品推荐

