如果方法包含等待调用,为什么我需要调用 StateHasChanged

Why do i need to call StateHasChanged if the method contains an await call

查看这段代码(blazor 服务器页面):

@page "/"

<div>@str</div>

<button @onclick="eventArgs => { OnClick(); }">Hello</button>

@code
{
   private string str { get; set; }
   private void OnClick()
   {
      str = "Hello world";
   }
}

此代码完美运行。当我点击按钮时,我可以在我的页面上看到“Hello world”。

现在看这段代码:

@page "/"

<div>@str</div>

<button @onclick="eventArgs => { OnClick(); }">Hello</button>

@code
{
   private string str { get; set; }
   private async Task OnClick()
   {
      await ReadDataBase();
      str = "Hello world";
      StateHasChanged();
   }
}

我不明白为什么,但如果我不放置 StateHasChanged(),str 不会立即刷新。我的问题是……为什么?

谢谢

原因是您没有 await在 lambda 中使用异步方法。换句话说,将您的代码更改为:

<button @onclick="async eventArgs => { await OnClick(); }">Hello</button>

话虽如此,如果您所做的只是调用异步方法而从未实际使用等待的任务,则可以删除括号 { } 并将代码更改为以下内容:

<button @onclick="() => OnClick()">Hello</button>

如果您这样做,框架将为您将 lambda 包装在 Func<Task> 中。

现在,我假设您使用 lambda 是有原因的,例如您想向它传递一些参数。如果不是这种情况,您也可以像下面这样编写代码:

<button @onclick="OnClick">Hello</button>

当您不等待异步方法时,将继续执行而不等待您的任务完成。基本上,这是您的代码中发生的事情:

  1. 点击按钮
  2. 调用了 ReadDataBase
  3. StateHasChanged 由框架调用
  4. str还没有更新,所以UI不刷新
  5. str 终于更新了
  6. 您需要手动调用 StateHasChanged 来更新 UI

您的版本缺少一个 return,这将有效:

<button @onclick="eventArgs => { return OnClick(); }">Hello</button>

您可以缩短为:

<button @onclick="eventArgs => OnClick()">Hello</button>

当没有参数时,您可以将其简化为

<button @onclick="OnClick">Hello</button>

在所有三种情况下,分配给 @onclick 的方法现在 returns 一个任务,将等待。然后你就不再需要 StatehasChanged(); 行了。

@onclick= 生成的代码适用于接受任务方法和(不)自动使用 eventArgs。

I do not understand why but the str is not refreshed immediately if i do not put StateHasChanged(). My question is ... why ?

这是部分正确的。 str 变量被分配了字符串“Hello world”,只有在对 ReadDataBase 的调用返回后页面才为 re-rendered。请注意,应等待对 OnClick 的调用,如下所示:

`@onclick="async eventArgs => { await OnClick(); }"`

但这不是“str 没有立即刷新”的原因。原因是在给 str 赋值之前调用了 ReadDataBase 方法...当您等待 ReadDataBase 方法时,调用代码将执行,并且框架执行其他任务...只有在 ReadDataBase 方法返回后(当任务完成时),OnClick 方法中的代码继续执行下一行,即字符串“Hello”的赋值world" 到 str,然后组件才为 re-rendered,如果您添加对 StateHasChanged 方法的调用,或者更好地执行 @onclick="async eventArgs => { await OnClick(); }" 并删除对 [=23 的调用=]

结论:如果要给str赋值并立即刷新显示,将str = "Hello world";放在第一行,await ReadDataBase();放在第二行。它无需调用 StateHasChanged 即可工作,即使您使用

<button @onclick="eventArgs => { OnClick(); }">Hello</button>

而不是

@onclick="async eventArgs => { await OnClick(); }"

顺便说一句,我会做这样的事情:

<button @onclick="eventArgs => OnClick()">Hello</button>

现在尝试了解 "eventArgs => OnClick()""eventArgs => { OnClick(); }"

之间的区别

试试这个代码:

<div>@str</div>

<button @onclick="OnClick">Click me</button>

@code
{
    private string str { get; set; }
    
    private async Task OnClick()
    {
        str = "Hello world";
        // Simulate an async call to ReadDataBase
        await Task.Delay(3000);

    }
}

更新和澄清:

我猜你知道什么时候不应该手动调用 StateHasChanged 方法,因此你的问题...确实,在过去,必须在组件状态发生变化后手动调用此方法,以便通知状态已更改的组件,它应该 re-render。后来,当涉及 UI 事件(例如 'click')时,手动调用 StateHasChanged 方法变得多余。其背后的原因是 UI 事件几乎总是导致组件状态的修改,因此让我们为开发人员节省手动添加它的负担。现在,由于您的 OnClick 方法是一个事件处理程序,因此不需要手动添加 StateHasChanged 方法,但如果不手动添加它,组件就不会刷新 (re-render)。出现该行为的原因是您同步调用 OnClick,而没有返回应告知 Blazor OnClick 方法已完成的 Task 对象。在这种情况下,Blazor 无法知道 OnClick 方法何时完成,因此它不会自动调用 StateHasChanged 方法。但是,当您手动添加 StateHasChanged 方法时,组件会刷新 (re-rendered)。当然也可以不手动添加,而是异步调用OnClick方法