StateHasChanged() 重新渲染组件两次

Pep*_*ger 4 c# asp.net razor blazor blazor-server-side

我正在做Blazor服务器端的项目,我想使一个按钮,desactivate点击后,却没有使用disabled的属性<button>。代码非常简单:

@functions {

    LogInForm logInForm = new LogInForm();
    bool IsDisabled;
    SignInResult result;

    protected override void OnInitialized()
    {
        IsDisabled = false;
    }

    async Task TryLogIn()
    {
        IsDisabled = true;
        StateHasChanged();
        result =  await _LogInService.TryLogIn(logInForm);
        Console.WriteLine("Logging status : " + (result.Succeeded ? "Sucess" : "Failure"));
        IsDisabled = false;
        StateHasChanged();
    }

}
Run Code Online (Sandbox Code Playgroud)

出于奇怪的原因,第一个StateHasChanged没有触发,但第二个确实重新呈现页面。通过进入调试模式并进入StateHasChanged()方法,它可以很容易地进行测试。第二次调用时,它会在进入方法后停止在 HTML 代码上,但不是第一次。

为什么这样 ?

注意:我不是在寻找任何使用Task.Delay或Task.Run(...)仅使用的解决方法,因为它在这些线程和 UI 刷新线程之间存在竞争条件,因此它不是一个可靠的解决方案。我正在寻找有关StateHasChanged()行为或解决方法的答案,方法是使用诸如PropertyChanged或之类的事件EventCallback并将按钮作为子组件。

编辑:经过一些测试,似乎StateHasChanged()只有await在对Task. 可以通过添加注释result = await _LogInService.TryLogIn(logInForm);行或更改IsDisabled = ...为来轻松测试它await new Task.Run(() => { IsDisabled = ...})。我现在有一些解决方法,但我仍然想知道为什么这是一个功能。不应该StateHasChanged()在任何操作后重新渲染?或者它认为只有async操作(所以主要是服务器调用)可以改变 UI 中的某些内容?

Pos*_*rte 7

Blazor 团队即将发布有关 StateHasChanged() 工作原理的文档。

你可以在这里跟踪它:https : //github.com/aspnet/AspNetCore/issues/14591

就目前而言,我认为摘自 github 评论的这个解释是一个很好的解释:

添加对 StateHasChanged 的​​调用只是将要呈现的组件排队。渲染器决定渲染何时发生。

这可能由 4 种情况触发:

  • 初始渲染,引导过程触发根组件及其所有子组件的初始渲染。
  • 一个事件,其中处理事件的组件会在事件发生后自动触发新的渲染,如果渲染新的子项或更改其参数,则可能会触发其子项。
  • 由于从 InvokeAsync 调用调用 StateHasChanged(本质上是编组回 UI 线程)
  • 作为父组件更改子组件参数的结果,当渲染器在子组件上调用 SetParametersAsync 时,这会作为差异过程的一部分发生。

非常清楚,调用 StateHasChanged 只会为组件排队渲染或“将其标记为脏”。

渲染器决定何时以及如何生成渲染。BuildRenderTree 不会产生新的渲染输出,只会在组件被调用时产生新的“V-DOM”定义。

通常,每个渲染批次(这是一起渲染/差异化并发送到 UI 进行更新的组件的集合)渲染一次组件。一个组件在一个批处理中渲染不止一次的情况只有两种:

  • 您有一个直接实现 IComponent 并调用 RenderHandle.Render 的组件
  • 您在子组件和父组件之间存在循环依赖,这可能会导致父组件作为子组件的一部分重新渲染,从父组件调用某些回调参数作为其初始化的一部分

来源:https : //github.com/aspnet/AspNetCore/issues/15175#issuecomment-544890549


Isa*_*aac 3

下面通过执行流程来描述重新渲染是如何发生的:

  1. 父组件调用StateHasChanged
  2. 生成一个新的渲染树
  3. 老树和新树之间的差异正在发生
  4. 传递给子组件的值被认为与其当前持有的值不同。
  5. 正在子组件上调用 SetParameters,以使用父组件传递给它的值来更新它们。

现在,当您在为局部变量 IsDisabled 赋值后调用 StateHasChanged 时,并不会真正更改组件的状态,并且调用 StateHasChanged 没有理由会产生重新渲染。当您调用 StateHasChanged 时,它只是将该组件的呈现请求排队。但没有理由重新渲染......

或者它认为只有异步操作(主要是服务器调用)才能更改 UI 中的某些内容?

它与操作的类型是否为异步无关,除了 OnInitializedAsync 方法之外,在这种情况下,当 OnInitializedAsync 方法完成时会自动调用 StateHasChanged 方法,以使用新数据再次重新渲染 UI由 OnInitializedAsync 方法中执行的异步调用检索。

更新:

您想要的可以通过多种方式完成,其中最简单的方式如下所示:

   <input type="button" value="Click me now"  disabled="@IsDisabled" @onclick="TryLogIn" />


@code{ 

    bool IsDisabled;

    protected override void OnInitialized()
    {
        IsDisabled = false;
    }

    async Task TryLogIn()
    {
        IsDisabled = true;

        // Do some async work here...
        // Note: Replace your async method with Task.Delay 
        await Task.Delay(5000);

        IsDisabled = false;

    }

}
Run Code Online (Sandbox Code Playgroud)

这应该有效...注意:禁用按钮控件的唯一方法是使用禁用属性

无需调用 StateHasChanged 方法。当编译器(编译器)为您的组件创建 EventCallback“委托”时,编译器插入源代码中的代码会自动调用它。

触发 UI 事件后会自动调用 StateHasChanged 方法。