我有这样一个组件:
<h3>@IsEnabled</h3>
@code {
[Parameter]
public bool IsEnabled { get; set; }
}
还有这样一个页面:
!!!旧代码,已更改代码,下面没有无限循环!!!
@page "/"
<PageTitle>Binding Test</PageTitle>
<BlazorBindingTest.Components.MyComponent IsEnabled="@IsEnabled"></BlazorBindingTest.Components.MyComponent>
@code
{
public bool IsEnabled { get; set; } = false;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
while (true)
{
await Task.Delay(1000);
IsEnabled = !IsEnabled;
//StateHasChanged(); // <<<<<<<<< Why is this neccessary?
}
}
}
}
而且我注意到,StateHasChanged在Blazor Server和WASM中似乎都是必要的。然而,我读到StateHasChanged可能会对整个渲染产生重大的负面影响。
为什么这里有必要,如何避免?组件/绑定不是应该自动更新吗?
我试过字段、属性、私有、公共等。
编辑
无限循环只是一个例子来说明这个问题,我已经修改了代码如下,这个问题自然会继续存在。这个问题只是关于StateHasChanged的需要:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_ = Task.Run(myInfinityLoop); // Do not await
}
}
private async Task myInfinityLoop()
{
while (true)
{
IsEnabled = !IsEnabled;
await Task.Delay(1000);
//StateHasChanged(); <<<< Still neccessary - Why?
}
}
3条答案
按热度按时间m1m5dgzv1#
为什么这里有必要,如何避免?组件/绑定不是应该自动更新吗?
回顾以下几点。您正在组件类中运行一个进程,并更改其内部状态:
IsEnabled
。没有组件观察器监视组件的内部状态,因此您必须通过调用StateHasChanged
来初始化更新过程。渲染器处理该请求的过程的一部分是检查子组件参数的状态。它在参数 * 可能已更改 * 的任何子组件上调用SetParametersAsync
。注意,渲染器无法检测对象中的更改,所以它假设它们已经改变。考虑要点:
1.你应该非常小心你在AfterRender方法中运行什么代码。[个人]我建议只运行JSInterop代码,除非你不得不这样做。
1.生命周期方法OnInitialized{Async}/OnParametersSet{Async}/OnAfterRender{Async}中的代码应[尽快]运行并完成。不良做法:这是一个无限循环,意味着第一个
OnAfterRenderAsync
永远不会完成。1.任何UI事件-按钮点击、输入、组件EventCallbacks -如果处理程序在等待和处理程序执行完成时产生,则自动调用
StateHasChanged
。1.你应该很少需要调用
StateHasChanged
。太多时候调用它是为了尝试和修复代码中的逻辑问题。修复了逻辑,调用StateHasChanged
的需要就消失了。例外情况见5。1.您需要在标准事件处理程序中调用
StateHasChanged
(例如下面的计时器超时事件)。1.当您更新组件状态并希望更新组件及其兄弟组件的UI时,需要调用
StateHasChanged
,因为没有观察器监视内部组件状态并在组件状态发生变化时触发渲染。1.对
StateHasChanged
的不必要调用触发渲染树级联。树中的组件越多,负载越高。下面是代码的一个略有不同的实现,它使用计时器来执行循环,并使用事件处理程序来驱动UI更新。
hof1towb2#
第二个答案解决了更新后的问题代码中的一些评论和问题。由于这是一个不同的问题,我添加了第二个答案,而不是一个很长的单一答案。
您是否在
StateHasChanged();
未注解的情况下运行过这段代码?[我添加了一些调试代码,以便您可以查看正在运行的线程。]
不会发生任何情况。如果选中
Output
,您将看到:这突出了这样做的一个问题:
发生异常的原因是,您通过调用
Task.Run
切换到线程池,然后尝试在Dispatcher上下文之外运行StateHasChanged
。将以下扩展名添加到Task:
然后
您将发现异常,并能够处理它。
您可以像这样解决异常:
我假设您这样做是为了从生命周期中“触发并忘记”循环,并让生命周期方法完成。
还有一种不同的方法,它不涉及
Task.Run
。这只是将任务分配给一个类变量,它还将解决异常错误,因为现在
StateHasChanged
将在Dispatcher上下文中运行。然后,您可以将调用移动到
OnInitialized
:cwdobuhd3#
Blazor会在检测到用户交互(如按钮单击等)时自动检测更改(属性值或字段)。这是因为Blazor内置了事件处理程序来检测用户交互。
但是,当您在UI事件处理程序之外更新属性或字段时(如在原始示例中,您在
OnAfterRenderAsync
方法中更新IsEnabled
属性),Blazor不知道发生了更改,您需要手动调用StateHasChanged()
来触发重新呈现。通常,不建议更改
OnAfterRenderAsync
内的参数,因为这可能导致意外的永久循环。您可以改用
EventCallback
: