我正在制作一个 Blazor 服务器端项目,我想制作一个单击后停用的按钮,但不使用 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();
    }

}

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

为什么这样 ?

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

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

最佳答案

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

  • 父组件调用 StateHasChanged
  • 生成新的渲染树
  • 老树和新树之间的差异正在发生
  • 传递给您的子组件的值被认为与其当前持有的值不同。
  • 在子组件上调用
  • SetParameters 以使用父组件传递给它的值更新它们。

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



    操作的类型是否为异步无关,除了 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;
    
        }
    
    }
    

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

    无需调用 StateHasChanged 方法。当编译器(编译器)为您的组件创建一个 EventCallback 'delegate' 时,它会被编译器插入到您的源代码中的代码自动调用。

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

    关于c# - StateHasChanged() 重新渲染组件两次,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58932854/

    10-17 00:40