Blazor点击Book Dates按钮后自动渲染MainLayout内倒计时组件问题
Blazor 全站计时器自动显示问题解决方案
问题根源
你的CountdownTimer组件作为MainLayout的一部分,仅在组件初始化(OnInitializedAsync)时获取一次预订数据。当用户点击「Book Dates」按钮创建新预订后,组件无法感知到数据变化,只有全页刷新时才会重新初始化并加载最新数据,导致无刷新导航时计时器不显示。
解决方案:使用共享状态服务实现跨组件通信
通过创建一个全局共享的状态服务,在预订创建完成后触发更新通知,让CountdownTimer组件实时获取最新数据并刷新UI。
步骤1:创建预订状态服务
这个服务负责维护预订状态变更的事件,其他组件可以订阅该事件以响应数据更新。
public class BookingStateService { // 定义预订更新事件 public event Action OnBookingUpdated; // 触发事件的方法 public void NotifyBookingUpdated() { OnBookingUpdated?.Invoke(); } }
步骤2:注册状态服务
在Program.cs中将服务注册为单例,确保全站共享同一个实例:
builder.Services.AddSingleton<BookingStateService>();
步骤3:修改CountdownTimer组件订阅事件
注入状态服务,订阅预订更新事件,在事件触发时重新加载数据并刷新UI:
@inject IBookingRepository bookingRepository @inject ProtectedLocalStorage localStorage @inject NavigationManager navigationManager @inject BookingStateService bookingStateService @implements IDisposable @if (booking is not null) { if (booking.CheckInDate != DateOnly.MinValue) { <div class="timer"> <p>Current booking time left: @timeSpan.ToString(@"mm\:ss")</p> </div> } } <br /> <br /> @code { private TimeSpan timeSpan; private bool isRunning = false; private DateTime startTime; private Timer timer; private ProtectedBrowserStorageResult<string> result { get; set; } public Booking? booking { get; set; } protected override async Task OnInitializedAsync() { await GetLocalStorage(); if (!isRunning && booking.CheckInDate != DateOnly.MinValue) { StartTimer(); } // 订阅预订更新事件 bookingStateService.OnBookingUpdated += HandleBookingUpdated; } // 处理预订更新事件 private async void HandleBookingUpdated() { await GetLocalStorage(); // 停止现有计时器(防止重复启动) if (isRunning) { StopTimer(); } // 如果存在有效预订,启动计时器 if (booking.CheckInDate != DateOnly.MinValue) { StartTimer(); } // 强制刷新UI await InvokeAsync(StateHasChanged); } private async Task GetLocalStorage() { result = await localStorage.GetAsync<string>(SettingsUtility.sessionId); if (!result.Success) { await localStorage.SetAsync(SettingsUtility.sessionId, Guid.NewGuid()); result = await localStorage.GetAsync<string>(SettingsUtility.sessionId); } bookingRepository.DeleteStaleBookings(); booking = bookingRepository.GetBySessionId(Guid.Parse(result.Value)); if (booking is null) { booking = new Booking(); booking.SessionId = Guid.Parse(result.Value); } } private void StartTimer() { startTime = booking.CreatedDate.AddSeconds(SettingsUtility.bookingTime); timer = new Timer(UpdateTime, null, 0, 1000); isRunning = true; } private void StopTimer() { timer?.Dispose(); isRunning = false; } private void UpdateTime(object state) { timeSpan = startTime - DateTime.UtcNow; InvokeAsync(StateHasChanged); if (timeSpan.TotalMilliseconds < 0) { StopTimer(); bookingRepository.DeleteStaleBookings(); } } // 组件销毁时取消订阅,避免内存泄漏 public void Dispose() { bookingStateService.OnBookingUpdated -= HandleBookingUpdated; StopTimer(); } }
步骤4:修改日历组件触发更新事件
在「Book Dates」按钮的提交方法中,创建预订完成后调用状态服务的通知方法:
@inject IBookingRepository _bookingRepository @inject BookingStateService bookingStateService <div class="calendar-days grid-7"> <CalendarDisabledDays calendarMonth="calendarMonth" offsetDays="offsetDays" /> @for (int i = startDay; i <= daysInMonth; i++) { DateTime date = new DateTime(calendarMonth.Year, calendarMonth.Month, i); <div class="active"> <button @onclick="() => SetDate(DateOnly.FromDateTime(date))">@i</button> </div> } </div> <div class="calendar-submit grid-1"> <EditForm OnSubmit="CreateDates" EditContext="editContext"> <div> <button type="reset" class="btn btn-main" @onclick="ClearDates">Clear Dates<i class="fa-solid fa-eraser btn-icon"></i></button> <button type="submit" class="btn btn-main">Book Dates<i class="fa-regular fa-calendar-check btn-icon"></i></button> </div> <div> <DataAnnotationsValidator /> <ValidationSummary /> </div> </EditForm> </div> @code { [Parameter] public int daysInMonth { get; set; } [Parameter] public bool status { get; set; } [Parameter] public DateOnly calendarMonth { get; set; } [Parameter] public int offsetDays { get; set; } [Parameter] public BookingDatesVM booking { get; set; } private int startDay { get; set; } EditContext? editContext; ValidationMessageStore? validationMessages; bool valid; protected override async Task OnInitializedAsync() { editContext = new(booking); validationMessages = new(editContext); await base.OnInitializedAsync(); } protected override Task OnParametersSetAsync() { SetStartDate(); return base.OnParametersSetAsync(); } private void SetStartDate() { startDay = 1; if ((calendarMonth.Month == DateTime.Now.Month) && (calendarMonth.Year == DateTime.Now.Year)) { startDay = DateTime.Now.AddDays(1).Day; } } private void SetDate(DateOnly date) { if (status) { booking.CheckInDate = date; } else { booking.CheckOutDate = date; } } private void ClearDates() { booking.CheckInDate = DateOnly.MinValue; booking.CheckOutDate = DateOnly.MinValue; } private async Task CreateDates() { valid = ValidateForm(); if (valid) { await _bookingRepository.CreateAsync(BookingMapper.FromDatesVM(booking)); // 触发预订更新通知 bookingStateService.NotifyBookingUpdated(); } } private bool ValidateForm() { valid = editContext.Validate(); validationMessages?.Clear(); var checkOutDate = editContext.Field(nameof(booking.CheckOutDate)); var checkInDate = editContext.Field(nameof(booking.CheckInDate)); if (booking.CheckInDate == DateOnly.MinValue) { validationMessages.Add(checkInDate, "Check in date is required."); valid = false; } if (booking.CheckOutDate == DateOnly.MinValue) { validationMessages.Add(checkOutDate, "Check out date is required."); valid = false; } return editContext.Validate(); } }
关键说明
- 状态服务通过事件机制实现跨组件通信,无需依赖组件层级关系,适合全站范围的状态更新通知。
CountdownTimer组件实现IDisposable接口,在销毁时取消事件订阅,避免内存泄漏。- 每次预订创建完成后触发通知,确保
CountdownTimer实时获取最新预订数据并自动启动计时器。
内容的提问来源于stack exchange,提问作者alex cardoso
相关产品推荐
相关产品推荐

