Blazor如何正确移除子组件 解决关闭后点击主组件按钮无法重现问题
背景
我有一个MainComponent组件包含多个按钮,点击按钮可唤起SubComponent子组件,调用代码如下:<TaskList AreaId="@SelectedAreaId" IsVisible="@IsVisible"></TaskList>
问题描述
我需要在SubComponent上添加关闭按钮,最初尝试在子组件内新增按钮执行IsVisible = !IsVisible,虽然可临时隐藏组件,但后续点击主组件对应按钮时该子组件无法再次显示,我推测是组件仍驻留内存未被正确移除。
我曾考虑给SubComponent实现IDisposable接口,但发现ComponentName.Dispose()方法未定义,请问最优解决方案是什么?
原有错误实现代码
主组件(TaskList.razor)
<div class="col-12 outer-div row"> <TaskDetails TaskId="@SelectedTaskId" IsVisible="@IsTaskDetailsVisible"></TaskDetails> </div> ... <ul> @for (int i = 0; i < MondayTasks.Count(); i++) { var myIndex = i; <li class="btn" @onclick="@(() => LoadTaskDetailsForTask(MondayTasks[myIndex].Id))"> <a role="button"> <h4>@MondayTasks[i].Name</h4> <p>@MondayTasks[i].DeadlineDate.Date.ToShortDateString()</p> </a> </li> } </ul> ... @code { private async Task LoadTaskDetailsForTask(int taskId) { SelectedTaskId = taskId; IsTaskDetailsVisible = true; await JSRuntime.InvokeVoidAsync("onScrollEvent"); } }
子组件(TaskDetails.razor)
@if (IsVisible) { ... <div class="row"> <h2 class="inner-div title">@taskDetails.Name</h2> <span class="closebtn" @onclick="()=> ToggleVisibility()">×</span> </div> // 展示业务信息 } ... @code { protected async override void OnParametersSet() { taskDetails = await _dbTasks.GetTasksDetails(TaskId); TaskItems = await _dbTasks.GetTasksItems(TaskId); this.StateHasChanged(); } private void ToggleVisibility() { IsVisible = !IsVisible; } }
问题根因
Blazor遵循单向数据流规则,父组件传递给子组件的参数属于父组件的状态,子组件直接修改自身接收的参数属性不会同步到父组件的状态值。你在子组件修改IsVisible为false后,父组件的IsTaskDetailsVisible仍为true,下次点击按钮触发IsTaskDetailsVisible = true时,Blazor认为参数值没有变化,不会触发子组件参数更新,因此子组件不会重新显示。
可行解决方案(级联参数实现)
你提供的最终实现通过级联参数将父组件实例传递给子组件,由子组件调用父组件的方法修改父级状态,符合单向数据流要求,可正常运行,代码如下:
主组件(TaskList.razor)
<CascadingValue Value="this"> <div class="col-12 outer-div row"> <TaskDetails TaskId="@SelectedTaskId" IsVisible="@IsTaskDetailsVisible"></TaskDetails> </div> </CascadingValue> ... <ul> @for (int i = 0; i < MondayTasks.Count(); i++) { var myIndex = i; <li class="btn" @onclick="@(() => LoadTaskDetailsForTask(MondayTasks[myIndex].Id))"> <a role="button"> <h4>@MondayTasks[i].Name</h4> <p>@MondayTasks[i].DeadlineDate.Date.ToShortDateString()</p> </a> </li> } </ul> ... @code { private async Task LoadTaskDetailsForTask(int taskId) { SelectedTaskId = taskId; IsTaskDetailsVisible = true; await JSRuntime.InvokeVoidAsync("onScrollEvent"); } public void RemoveTaskDetails() { IsTaskDetailsVisible = false; StateHasChanged(); } }
子组件(TaskDetails.razor)
@if (IsVisible) { ... <div class="row"> <h2 class="inner-div title">@taskDetails.Name</h2> <span class="closebtn" @onclick="()=> ToggleVisibility()">×</span> </div> // 展示业务信息 } ... @code { [CascadingParameter] public TaskList _Parent { get; set; } protected async override void OnParametersSet() { taskDetails = await _dbTasks.GetTasksDetails(TaskId); TaskItems = await _dbTasks.GetTasksItems(TaskId); this.StateHasChanged(); } private void ToggleVisibility() { _Parent.RemoveTaskDetails(); } }
更优解耦方案(EventCallback实现)
上述级联参数方案可正常运行,但会造成父子组件强耦合,更推荐使用Blazor原生的EventCallback实现状态同步,无需传递父组件实例,复用性更强:
- 子组件新增回调参数
[Parameter] public EventCallback OnClose { get; set; }
- 子组件关闭按钮点击事件改为触发回调
private async Task ToggleVisibility() { await OnClose.InvokeAsync(); }
- 父组件绑定回调修改自身状态
<TaskDetails TaskId="@SelectedTaskId" IsVisible="@IsTaskDetailsVisible" OnClose="() => IsTaskDetailsVisible = false" ></TaskDetails>
该方案代码更简洁,耦合度更低,是Blazor父子组件通信的标准实践。
内容的提问来源于stack exchange,提问作者Henrique Pombo

