StateHasChanged()两次渲染组件一次

时间:2019-11-19 11:18:35

标签: c# asp.net razor blazor blazor-server-side

我正在制作一个Blazor服务器端项目,我想创建一个单击后便停用的按钮,但不使用disabled的{​​{1}}属性。代码很简单:

<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(); } } 没有触发,但是第二个确实重新渲染了页面。通过进入Debug模式并进入StateHasChanged方法,可以很容易地进行测试。在第二次调用时,它会在进入方法后停止在HTML代码上,但不是第一次。

为什么?

NB:我不是在寻找仅使用StateHasChanged()Task.Delay的变通办法,因为它存在那些线程与UI刷新线程之间的竞争状态,并且因此这不是一个可靠的解决方案。我正在寻找Task.Run(...)行为或解决方法的答案,方法是使用StateHasChanged()PropertyChanged之类的事件并将按钮作为子组件。

编辑:经过一些测试,看来EventCallback仅在对StateHasChanged()进行await操作之后才触发组件的重新渲染。可以通过在Task行中添加注释或将result = await _LogInService.TryLogIn(logInForm);更改为IsDisabled = ...来轻松地对其进行测试。我现在有一些解决方法,但是我仍然想知道为什么这是一个功能。 await new Task.Run(() => { IsDisabled = ...})是否应在执行任何操作后重新渲染?还是认为只有StateHasChanged()个操作(因此大部分是服务器调用)才能更改UI中的某些内容?

2 个答案:

答案 0 :(得分:3)

Blazor团队将发布有关StateHasChanged()工作方式的文档。

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

就目前而言,我认为从github评论中得到的解释是一个很好的解释:

  

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

     

这可以由以下四种情况触发:

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

非常清楚,调用StateHasChanged仅将Render排队。   组件或“将其标记为脏”。

     

由渲染器决定何时以及如何生成   渲染。 BuildRenderTree不会导致新的渲染输出   在组件的“ V-DOM”的新定义中   被呼叫。

     

通常,每个渲染批处理一次渲染一个组件(这是一个   一起渲染/差异化并发送到的组件的集合   用于更新的用户界面)。在只有两种情况下,组件   批量渲染多次:

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

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

答案 1 :(得分:1)

以下是描述重新渲染如何发生的执行流程:

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

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

  

或者它认为只有异步操作(因此大部分是服务器调用)才能   更改用户界面中的某些内容?

除了OnInitializedAsync方法外,操作的类型是否异步均无关紧要,在这种情况下,当OnInitializedAsync方法完成以再次重新呈现UI时,将自动调用StateHasChanged方法。异步调用检索到的新数据将在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;

    }

}

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

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

在触发UI事件后,将自动调用StateHasChanged方法。