在 Blazor 中,是否有一种方法可以撤消无效的用户输入而不更改状态?

nos*_*tio 8 .net c# asp.net blazor blazor-webassembly

在 Blazor 中,如何撤消无效的用户输入,而不更改组件的状态以触发重新渲染?

这是一个简单的 Blazor 计数器示例(在线尝试):

<label>Count:</label>
<button @onclick=Increment>@count times!</button><br>
A: <input @oninput=OnChange value="@count"><br>
B: <input @bind-value=count @bind-value:event="oninput">

@code {
    int count = 1;

    void Increment() => count++;

    void OnChange(ChangeEventArgs e)
    {
        var userValue = e.Value?.ToString(); 
        if (int.TryParse(userValue, out var v))
        {
            count = v;
        }
        else 
        {
            if (String.IsNullOrWhiteSpace(userValue))
            {
                count = 0;
            }

            // if count hasn't changed here,
            // I want to re-render "A"

            // this doesn't work
            e.Value = count.ToString();

            // this doesn't work either 
            StateHasChanged();           
       }
    }
}
Run Code Online (Sandbox Code Playgroud)

对于input元素 A,我想复制元素 B 的行为input,但不使用bind-xxx-style 数据绑定属性。

例如,当我123x在 A 中输入内容时,我希望它自动恢复为123B 中的情况。

我已经尝试过StateHasChanged,但我想它不起作用,因为count属性实际上并没有改变。

所以,基本上我需要重新渲染 A 来撤消无效的用户输入,即使状态没有改变。没有魔法我怎么能做到这一点呢bind-xxx

当然,bind-xxx这很棒,但在某些情况下,可能需要非标准行为,围绕托管事件处理程序(如ChangeEvent.


更新后,为了比较,我可以在 React 中完成以下操作(在线尝试):

function App() {
  let [count, setCount] = useState(1);
  const handleClick = () => setCount((count) => count + 1);
  const handleChange = (e) => {
    const userValue = e.target.value;
    let newValue = userValue ? parseInt(userValue) : 0;
    if (isNaN(newValue)) newValue = count;
    // re-render even when count hasn't changed
    setCount(newValue); 
  };
  return (
    <>
      Count: <button onClick={handleClick}>{count}</button><br/>
      A: <input value={count} onInput={handleChange}/><br/>
    </>
  );
}
Run Code Online (Sandbox Code Playgroud)

另外,我可以在 Svelte 中完成此操作,我发现它在概念上非常接近 Blazor(在线尝试)。

<script>
  let count = 1;
  const handleClick = () => count++;
  const handleChange = e => {
    const userValue = e.target.value;
    let newValue = userValue? parseInt(userValue): 0;
    if (isNaN(newValue)) newValue = count;
    if (newValue === count)
      e.target.value = count; // undo user input
    else
      count = newValue; 
    }
  };    
</script>

Count: <button on:click={handleClick}>{count}</button><br/>
A: <input value={count} on:input={handleChange}/><br/>
Run Code Online (Sandbox Code Playgroud)

更新了,澄清一下,我只是想通过处理更改事件来撤消我认为无效的输入,在发生后回顾性输入,而不改变组件的状态本身(counter此处)。

也就是说,没有 Blazor 特定的双向数据绑定、HTML 本机type=number或模式匹配属性。我这里只是以数字格式要求为例;我希望能够撤消任何类似的任意输入。

我想要的用户体验(通过 JS 互操作 hack 完成):https://blazorrepl.telerik.com/wPbvcvvi128Qtzvu03

与其他框架相比,Blazor 中的这一点令人惊讶,而且我无法使用它StateHasChanged来简单地强制重新渲染当前状态的组件。

Lie*_*ero 3

所以,基本上我需要重新渲染 A 来撤消无效的用户输入,即使状态没有改变。如果没有 bind-xxx 魔法,我该如何做到这一点?

您可以通过更改指令中的值来强制重新创建和重新渲染任何元素/组件@key

<input @oninput=OnChange value="@count" @key="version" />
Run Code Online (Sandbox Code Playgroud)
<input @oninput=OnChange value="@count" @key="version" />
Run Code Online (Sandbox Code Playgroud)

请注意,它将重新渲染整个元素(及其子树),而不仅仅是属性。


您的代码的问题是,组件的 BuildRendrerTree 方法生成完全相同的 RenderTree,因此 diffing 算法在实际 dom 中找不到任何要更新的内容。

那么为什么 @bind 指令有效呢?

注意生成的 BuildRenderTree 代码:

void OnChange(ChangeEventArgs e)
{
    var userValue = e.Value?.ToString(); 
    if (int.TryParse(userValue, out var v))
    {
        count = v;
    }
    else 
    {
        version++;
        if (String.IsNullOrWhiteSpace(userValue))
        {
            count = 0;
        }         
   }
}
Run Code Online (Sandbox Code Playgroud)

诀窍是,@bind 指令添加了:

__builder.SetUpdatesAttributeName("value");
Run Code Online (Sandbox Code Playgroud)

您现在无法在 EventCallback 的标记中执行此操作,但有一个未解决的问题: https: //github.com/dotnet/aspnetcore/issues/17281

但是,您仍然可以创建 Component 或 RenderFragment 并__builder手动编写代码。