Blazor文本字段输入用户键入延迟

Atr*_*rox 6 c# throttling debounce blazor blazor-server-side

如何在Blazor中为事件(OnInput)添加延迟?
例如,如果用户正在文本字段中输入,而您要等到用户完成输入。

Blazor.Templates :: 3.0.0-preview8.19405.7

码:

@page "/"
<input type="text" @bind="Data" @oninput="OnInputHandler"/>
<p>@Data</p>

@code {    
    public string Data { get; set; }   

    public void OnInputHandler(UIChangeEventArgs e)
    {
        Data = e.Value.ToString();
    }    
}
Run Code Online (Sandbox Code Playgroud)

dan*_*era 36

解决方案:

您的问题没有单一的解决方案。以下代码只是一种方法。看一看并根据您的要求进行调整。代码在每个 上重置一个计时器keyup,只有最后一个计时器引发OnUserFinish事件。记得通过实现来处理定时器IDisposable

@using System.Timers;
@implements IDisposable;
...
<input type="text" @bind-value="Data" @bind-value:event="oninput" 
       @onkeyup="@HandleKeyUp"/>
<p>@Data2</p>

@code {
    public string Data { get; set; }   
    public string Data2 { get; set; }  
    private Timer aTimer;

    protected override void OnInitialized()
    {
        aTimer = new Timer(1000);
        aTimer.Elapsed += OnUserFinish;
        aTimer.AutoReset = false;
    }

    void HandleKeyUp(KeyboardEventArgs e)
    {
        // remove previous one
        aTimer.Stop();

        // new timer
        aTimer.Start();        
    }    

    private void OnUserFinish(Object source, ElapsedEventArgs e)
    {
        InvokeAsync( () =>
          {
            Data2 = $"User has finished: {Data}";
            StateHasChanged();
          });
    }
    void IDisposable.Dispose()
    {
        aTimer?.Dispose();
    }
}
Run Code Online (Sandbox Code Playgroud)

用例:

此代码用例的一个示例是避免后端请求,因为在用户停止输入之前不会发送请求。

跑步:

示例代码运行

  • 您好感谢您的回答和解决方案!这正是我正在寻找的,我想我必须添加 Javascript :P 作为提示,当您添加 `@using System.Timers;` 时,此源代码将起作用。 (2认同)

Goo*_*ide 6

这个答案是之前答案之间的中间地带,即在DIY使用成熟的反应式 UI 框架之间

它利用了强大的Reactive.Extensions库(又名Rx),在我看来,这是在正常情况下解决此类问题的唯一合理方法。

解决方案

安装 NuGet 包后,System.Reactive您可以在组件中导入所需的命名空间:

@using System.Reactive.Subjects
@using System.Reactive.Linq
Run Code Online (Sandbox Code Playgroud)

Subject在您的组件上创建一个字段,作为输入事件和Observable管道之间的粘合剂:

@code {
    private Subject<ChangeEventArgs> searchTerm = new();
    // ...
}
Run Code Online (Sandbox Code Playgroud)

Subject与您的连接input

<input type="text" class="form-control" @oninput=@searchTerm.OnNext>
Run Code Online (Sandbox Code Playgroud)

最后,定义Observable管道:

@code {
    // ...

    private Thing[]? things;

    protected override async Task OnInitializedAsync() {
        searchTerm
            .Throttle(TimeSpan.FromMilliseconds(200))
            .Select(e => (string?)e.Value)
            .Select(v => v?.Trim())
            .DistinctUntilChanged()
            .SelectMany(SearchThings)
            .Subscribe(ts => {
                things = ts;
                StateHasChanged();
            });
    }

    private Task<Thing[]> SearchThings(string? searchTerm = null)
        => HttpClient.GetFromJsonAsync<Thing[]>($"api/things?search={searchTerm}")
}
Run Code Online (Sandbox Code Playgroud)

上面的示例管道将...

  • 给用户 200 毫秒的时间来完成输入(又名去抖动限制输入),
  • ChangeEventArgs, 中选择键入的值
  • 修剪它,
  • 跳过与最后一个相同的任何值,
  • 使用到目前为止的所有值来发出 HTTP GET 请求,
  • 将响应数据存储在字段上things
  • 最后告诉组件它需要重新渲染。

如果您的标记中有类似以下内容,您会在键入时看到它正在更新:

@foreach (var thing in things) {
    <ThingDisplay Item=@thing @key=@thing.Id />
}
Run Code Online (Sandbox Code Playgroud)

补充说明

不要忘记清理

您应该像这样正确处理事件订阅:

@implements IDisposable // top of your component

// markup

@code {
    // ...

    private IDisposable? subscription;

    public void Dispose() => subscription?.Dispose();

    protected override async Task OnInitializedAsync() {
        subscription = searchTerm
            .Throttle(TimeSpan.FromMilliseconds(200))
            // ...
            .Subscribe(/* ... */);
    }
}
Run Code Online (Sandbox Code Playgroud)

Subscribe()实际上返回一个IDisposable你应该与你的组件一起存储和处理的。但千万不能使用using它,因为这会过早破坏订阅。

开放问题

有些事情我还没有弄清楚:

  • 是否可以避免调用StateHasChanged()
  • 是否可以避免像在 Angular 中使用管道那样Subscribe()直接调用和绑定到Observable标记内部?async
  • 是否可以避免创建Subject? Rx 支持Observable从 C# Events创建s ,但如何获取oninput事件的 C# 对象?