Blazor如何访问重复渲染Razor组件的taskList参数
问题根因
你现在访问不到复用Todo组件内部taskList的本质原因是Blazor的组件隔离设计:
- 你写在Todo组件里的
taskList是protected级别的内部字段,没有标记[Parameter]特性,父组件无法直接通过组件传参访问 - 即便你用
@ref获取子组件实例直接读这个字段,也属于反模式:子组件内部修改列表时父组件不会收到变更通知,UI不会自动同步,后续非常容易出状态不一致的bug。
Blazor推荐的做法是状态提升:把需要跨组件访问的状态放到共同的父组件持有,子组件只负责接收数据、上报用户操作,由父组件统一修改状态。
实现步骤
1. 改造Todo子组件
把原来组件内部持有的taskList改成接收父组件传参,所有对列表的修改操作通过EventCallback通知父组件执行,顺便修复你原来代码里点击切换任务完成状态的逻辑bug(原来写的taskitem.IsComplete = task.IsComplete是错误逻辑,会导致状态异常)。
改造后的Todo组件代码:
<h3>@Headername</h3> <EditForm Model="task" class="form-inline" OnValidSubmit="HandleAddTask"> <input type="text" class="form-control mx-2" @bind-value="task.TaskName" /> <button class="btn btn-outline-primary mx-2">Add Task</button> </EditForm> <div class="progress-bar my-2" role="progressbar" style="width:@(PercentageCompleted)%" aria-valuemax="100" aria-valuemin="0"> @(PercentageCompleted)% </div> <ul class="list-group my-2"> @foreach (var taskitem in TaskList) { if (taskitem.IsComplete) { <li class="list-group-item list-group-item-success mx-1" @onclick="() => ToggleTask(taskitem)"> <del>@taskitem.TaskName</del></li> } else { <li class="list-group-item list-group-item-secondary mx-1" @onclick="() => ToggleTask(taskitem)">@taskitem.TaskName</li> } } </ul> @code { [Parameter] public string Headername { get; set; } = "Todo"; // 接收父组件传入的任务列表 [Parameter] public List<TaskModel> TaskList { get; set; } = new(); // 新增任务事件,通知父组件往列表加数据 [Parameter] public EventCallback<TaskModel> OnTaskAdded { get; set; } // 切换任务状态事件,通知父组件修改对应任务的完成状态 [Parameter] public EventCallback<TaskModel> OnTaskToggled { get; set; } private TaskModel task = new TaskModel(); private int PercentageCompleted { get { return TaskList.Count > 0 ? (TaskList.Count(x => x.IsComplete) * 100 / TaskList.Count) : 0; } } private async Task HandleAddTask() { await OnTaskAdded.InvokeAsync(task); task = new TaskModel(); } private async Task ToggleTask(TaskModel taskItem) { taskItem.IsComplete = !taskItem.IsComplete; await OnTaskToggled.InvokeAsync(taskItem); } }
注意:把Todo组件顶部多余的@page "/Tests"去掉,可复用组件不需要加路由指令,只有独立页面才需要。
2. 改造ToDos父页面
在父页面持有两个独立的任务列表,传给对应的Todo组件,处理子组件的事件更新列表,页面底部可以直接读取两个列表的内容输出:
@page "/Todo" <div class="row"> <div class="col-6"> <Todo Headername="Daily Task" TaskList="dailyTasks" OnTaskAdded="AddDailyTask" OnTaskToggled="() => StateHasChanged()"> </Todo> </div> <div class="col-6"> <Todo Headername="Monthly Task" TaskList="monthlyTasks" OnTaskAdded="AddMonthlyTask" OnTaskToggled="() => StateHasChanged()"> </Todo> </div> </div> <div class="mt-4"> <h4>所有任务汇总</h4> <div class="row"> <div class="col-6"> <h5>Daily Task 列表(共@dailyTasks.Count项,已完成@dailyTasks.Count(x=>x.IsComplete)项)</h5> <ul> @foreach (var item in dailyTasks) { <li>@item.TaskName - @(item.IsComplete ? "已完成" : "未完成")</li> } </ul> </div> <div class="col-6"> <h5>Monthly Task 列表(共@monthlyTasks.Count项,已完成@monthlyTasks.Count(x=>x.IsComplete)项)</h5> <ul> @foreach (var item in monthlyTasks) { <li>@item.TaskName - @(item.IsComplete ? "已完成" : "未完成")</li> } </ul> </div> </div> </div> @code { // 父组件统一持有两个列表的状态 private List<TaskModel> dailyTasks = new(); private List<TaskModel> monthlyTasks = new(); private void AddDailyTask(TaskModel newTask) { dailyTasks.Add(newTask); } private void AddMonthlyTask(TaskModel newTask) { monthlyTasks.Add(newTask); } }
不推荐的方式(仅作参考)
如果只是临时测试想直接拿子组件内部的taskList,可以给Todo组件加@ref获取组件实例,直接访问修饰符改为public的taskList字段,但这种方式子组件内部更新列表时父组件不会自动刷新,必须手动触发StateHasChanged,实际项目不要这么写:
<Todo @ref="dailyTodoRef" Headername="Daily Task"></Todo> <Todo @ref="monthlyTodoRef" Headername="Monthly Task"></Todo> @code { private Todo dailyTodoRef; private Todo monthlyTodoRef; // 访问方式:dailyTodoRef.taskList,注意要先把Todo组件里taskList的访问修饰符从protected改成public }
内容的提问来源于stack exchange,提问作者GRAGW
相关产品推荐
相关产品推荐

