我有一个ImagesContainer
组件,显示ImageCard
组件的分页列表,它是从数据库服务调用填充的。当用户选择一个新页面时,我调用该服务并获取与页码和页面大小相关的更新列表。
由于我设置的一些标记条件(在页面加载和其他场景中显示微调器等),我必须依赖await InvokeAsync(StateHasChanged)
来更新页面。
现在我想实现一个选择系统,在ImageCard
上用复选框选择一些图像。我有一个Manager Service
,它处理应用程序状态和事件,它包含一个List<int>
,包含所选图像的id,以及相关的方法来添加,移除并清除复选框状态更改时从ImageCard
调用的列表。根据所述列表元素计数隐藏交互按钮。
在ImagesContainer
从分页更新后,我需要更新其中的每个组件以反映复选框的状态,否则用户选中的子ImageCard
组件将保留其选中状态,而不管它的新Image.Id
是否存在于Manager Service
的选定图像列表中,即使实际的IMG元素在组件上改变。
为了处理这个问题,我创建了一个OnRefreshImages
事件,它是通过Manager Service
从分页方法触发的,并由ImageCard
使用,当它在组件中被调用时,我检查卡片的Image.Id是否存在于选定的图像列表中,并将其设置为选中状态。所以最后,这是我为LoadImages()
编写的伪代码:
private async Task LoadImages()
{
// Call Database Service to fetch current page's Image entities
// Updates the page with the new image list
await InvokeAsync(StateHasChanged);
// Trigger event to refresh ImagesContainer´s ImageCard child components in order to set checked state
ManagerService.RefreshImages();
}
问题发生在StateHasChanged
调用之后,它似乎以同步的“fire and forget”方式运行(我假设这是因为StateHasChanged
返回void而不是Task),因为OnRefreshImages
事件在页面重新呈现之前被触发,并且在这一点上,ImagesContainer
仍然保存着分页之前的旧ImageCard
组件。
在两个调用之间插入一个await Task.Delay(1000)
解决了这个问题,现在在重新填充的容器上调用了OnRefreshImages
,但是具有任意的硬编码延迟并不是很漂亮,这是一个糟糕的用户体验,因为延迟的状态更新。
我是否应该在调用数据库服务后启动Image模型中的IsSelected
属性?这是否会阻止在StateHasChanged
期间自动生成OnRefreshImages
事件并更新ImageCard
组件的需要?
编辑:关于包含ImagesContainer
的Gallery
页面标记的更多上下文:
@if (_projects == null)
{
<LoadingSpinner />
}
else if (_projects.Count() == 0 && ManagerService.State.Gallery.FolderId == 0)
{
<CreateProjectForm />
}
else
{
if (images != null && images.Images != null && images.Images.Count > 0)
{
<ImagesContainer @ref=_imagesContainer Images="@images" />
}
else if (images.Images.Count == 0)
{
<MudText Typo="Typo.h5">Empty Project</MudText>
}
else
{
<LoadingSpinner />
}
}
应用程序使用“Projects”作为文件夹来保持图像的组织。这里我展示了一个微调器,同时_projects
变量被OnAfterRenderAsync
覆盖填充(不记得为什么我选择这个而不是OnInitializedAsync
,但也许这就是问题所在)。从数据库中获取项目后,我调用LoadImages()
来填充相关的项目图像。StateHasChanged
调用主要用于在初始化后重新呈现页面,因为由于某种原因,在从数据库调用初始化_projects
变量后,页面不会刷新,并且它会卡住,显示加载微调器,好像_projects
仍然为null。ManagerService.RefreshImages()
目的只是一个触发器,让管理器在ImageContainer
更新后,通过加载新的分页页面或其他交互来传播OnRefreshImages
的事件以刷新ImageCard
组件。这是实现:public void RefreshImages() => OnRefreshImages?.Invoke();
1条答案
按热度按时间umuewwlo1#
在没有查看实际代码的情况下,我只是做了一些有根据的猜测。
您应该只需要调用
StateHasChanged
来:1.如果要在Ui事件运行时显示状态,请在UI事件期间更新UI。
1.在事件处理程序中,如上面所述。
在几乎所有其他情况下,对
StateHasChanged
的调用都会强制更新UI,以掩盖逻辑问题(这些问题通常会在以后困扰您!)“由于我设置的一些标记条件(在页面加载和其他情况下显示微调器等),我必须依赖await InvokeAsync(StateHasChanged)在此之后更新页面。”
听起来你的逻辑可能是错误的,除非上面的1适用。
调用
StateHasChanged
不会呈现组件:它将一个RenderFragment
在渲染器的队列中排队。渲染器只有在获得线程时间时才能为它的队列提供服务。LoadImages
是一个同步代码块, Package 在一个Task
中。StateHasChanged
是一个同步函数--它只是加载队列。ManagerService.RefreshImages();
看起来像一个同步方法,所以渲染器只在LoadImages
完成后获得线程时间。添加awaitTask.Delay
通过产生并给予渲染器线程时间来解决这个问题。您只需要一个Delay(1)
。await Task.Yield()
也可以工作。