Blazor 绑定不一致的行为

Blazor binding inconsistent behaviour

我正在构建一个 blazor 应用程序 (WASM),我在其中使用了一些 <input> 和这些输入。我绑定了一个变量,为了便于浏览,我把它放在(索引)页面的@code部分。

在第二个 <input> 上,我还放置了一个 onclick 事件,在 onclick 事件中,我用第一个 (FirstText) 填充了第三个变量 (ThirdText)。

当我启动应用程序时,我点击第一个 <input>,并填写一些字符,然后点击第二个 <input>,第三个变量的值仍然为空。但是当我回到第一个,稍微改变一下,然后点击第二个,它直接填充了NEW值。

最后的行为也是我在第一种情况下所希望的

我的代码在 index.cs:

@page "/"

<h3>@FirstText</h3> <br/>
<h3>@SecondText</h3> <br/>
<h3>@ThirdText</h3> <br/>

<input @bind="FirstText" style="width:50%" /> <br/>
<input @bind="SecondText" @onclick="OnclickHandler" style="width:50%" /> <br/>

@code{
    private string FirstText { get; set; } = string.Empty;
    private string SecondText { get; set; } = string.Empty;
    private string ThirdText { get; set; } = string.Empty;

    void OnclickHandler(MouseEventArgs mouseEventArgs)
    {
        ThirdText = FirstText;
    }
}

这段代码是一个简单的例子,实际的应用程序有更多的逻辑,但这是我的问题的基础。

当我将第一个框设为空,然后单击第二个框时,它再次无法正常工作。

我已经尝试先在 oninitialised() 中填充第 3 个变量,但是第一个变量也是空的,所以这不起作用。

我知道在javascript中有一个类似的问题,那就是因为首先调用了onclick,然后忘记了要在第一个框退出时使用的onexit或其他事件。但在那种情况下,它总是出错。我希望 blazor 有针对这种情况的解决方案。因为如果第一个输入已经填满,它在 Blazor 中运行良好。

#第三个答案:

这是一个以最简单形式捕获问题的测试页。

我已经删除了大部分原始代码并使其成为一个简单的数据输入和保存表单 - 没有使用 EditForm

在当前配置中,当您输入姓名并单击保存按钮时,不会调用 SaveData

注释掉表格前的 <h3>@this.YourName</h3> 行,它现在可以工作了。请注意,表格下方的第二个 <h3>@this.YourName</h3> 没有任何影响。

现在将行更改为:

<h3>Your name is: @this.YourName</h3>

而且有效。

答案就在@JesseGood提供的答案中引用他的评论:

当您删除单行代码时,元素不再向下移动,因此单击事件发生在按钮上。但是,当您添加代码行时,该元素会被填充,导致所有其他元素向下移动,并且当您将手指从鼠标上移开时,该元素不再位于鼠标指针下方,从而导致点击事件发生在父元素。

@page "/"

@using System.Text

<PageTitle>Index</PageTitle>

@*Comment out this line and the event triggers on first click*@
<h3>@this.YourName</h3>

<h3>Your Name is @DisplayName</h3>

<div class="m-2">
    Your Name: <input value="@this.YourName" @onchange=this.NameChange style="width:50%" />
</div>
<div class="m-2">
    <button class="btn btn-primary" @onclick="SaveData">Save</button> <br />
</div>

<h3>@YourName</h3>

<div>
    <pre>
        @log.ToString()
    </pre>
</div>
@code {
    private string YourName { get; set; } = string.Empty;
    private StringBuilder log = new StringBuilder();
    private string DisplayName = string.Empty;
    
    void NameChange(ChangeEventArgs e)
    {
        this.YourName = e.Value?.ToString() ?? String.Empty;
        log.AppendLine("FirstChange Called");
    }

    void SaveData(MouseEventArgs mouseEventArgs)
    {
        this.DisplayName = this.YourName;
        log.AppendLine("Value Saved");
    }
}

关注答案:

当您在 Razor 中“绑定”时,Razor 编译器实际上构建了以下代码。在这个例子中,我只是手动连接它,所以我可以在引发 onchange 事件时输出一些调试代码。

__builder.OpenElement(15, "input");
__builder.AddAttribute(16, "style", "width:50%");
__builder.AddAttribute(17, "value", Microsoft.AspNetCore.Components.BindConverter.FormatValue(this.FirstText));
__builder.AddAttribute(18, "onchange", Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => this.FirstText = __value, this.FirstText));
__builder.SetUpdatesAttributeName("value");
__builder.CloseElement();

First and second Answer replaced

您的代码无法正常工作的原因是点击事件的时间安排:

If the button is pressed on one element and the pointer is moved outside the element before the button is released, the event is fired on the most specific ancestor element that contained both elements.

click fires after both the mousedown and mouseup events have fired, in that order.

source

长话短说,到 mouseup(甚至 mousedown 发生时),布局已更改并且输入元素已移出鼠标指针的范围。例如,将您的代码更改为以下内容,您将看到它现在可以工作了,因为元素没有移动:

<h3>AAA @FirstText</h3> <br/>
<h3>AAA @SecondText</h3> <br/>
<h3>AAA @ThirdText</h3> <br/>

您的问题的解决方案取决于您要实现的目标,但这里的基本思想是在发生布局更改时不依赖点击事件。

要真正证明发生了什么,请在 Chrome 开发工具中转到 Sources > Event Listener Breakpoints > Mouse > click 并确保选中该复选框。之后,在第一个输入中键入内容,然后单击第二个输入元素。单击事件 触发,但请注意下面屏幕截图中的事件目标!它不是输入元素而是父元素<article>。这证明 click 事件从未被调度到 input 元素,因为当 mouseup 事件发生时它 not 在鼠标指针下方。

你描述了当前的行为,但同时你对你的期望有点含糊。据我所知,代码可以满足您的要求。

当用户使用 Tab 键转到下一个输入时,您期望(想要)发生什么?是关于鼠标点击还是关于完成输入?

我认为您应该考虑以下两个更改之一:

<input @bind="FirstText" style="width:50%" /> <br/>
<input @bind="SecondText" @onfocus="OnUpdateHandler" style="width:50%" />

或者,这似乎更符合逻辑:

<input @bind="FirstText" @onblur="OnUpdateHandler" style="width:50%" />
<input @bind="SecondText"  style="width:50%" />

void OnUpdateHandler()
{
    ThirdText = FirstText;
}

如果这不起作用,请更具体:when/why您希望 ThirdText 镜像 FirstText 吗?