Blazor propertychange 奇怪的行为

Blazor propertychange strange behavior

我创建了一个可搜索的多 select 下拉菜单。我不确定这是否是我的代码中可能存在的错误或错误。

要重现,请执行以下操作:

1 Select 第一项

2 在输入搜索框中输入“o”或“one”

3 FilteredItems 的第一个复选框被选中,尽管它不在 SelectedItems

4 清除输入搜索框后再次输入“o”时显示未选中

<div>
<div class="form-group">
    <button class="btn dropdown-toggle btn-light"
            @onclick="@ToggleSelectMenu"
            title="@ButtonHoverTitleText">
        @ButtonText
    </button>
</div>
<div hidden="@toggleSelectBox" class="shadow p-2 mb-5 bg-white rounded">
    <div class="m-1">
        <input class="form-control" @bind="FilterText" @bind:event="oninput" />
    </div>
    @foreach (var item in FilteredItems)
    {
        <label class="item">
            <input class="form-check-input" type="checkbox" checked="@item.IsSelected"
               @onchange="_e => {SelectionChanged(item.Item, _e.Value);}" /><span>@item.Item</span>
        </label>
        <br />
    }
</div>
</div>
    <p>
        @string.Join(",", SelectedItems)
    </p>

@code {
    private string? _filterText;
    private bool toggleSelectBox = true;

    [Parameter] public List<string> Items { get; set; } = new List<string>() { "item", "one 1", "two 3", "last 4" };
    [Parameter] public List<string> SelectedItems { get; set; } = new();
    [Parameter] public EventCallback<List<string>> SelectedItemChanged { get; set; }
    public List<SelectedItem> FilteredItems { get; set; } = new();
    public string? ButtonText { get; set; } = "Nothing selected";
    public string? ButtonHoverTitleText { get; set; }

    public string? FilterText
    {
        get => _filterText;
        set
        {
            _filterText = value;
            FilteredItems = new();
            if (!string.IsNullOrWhiteSpace(_filterText))
            {
                foreach (var item in Items?.Where(x => x.ToLower().Contains(_filterText.ToLower())))
                {
                    FilteredItems.Add(new SelectedItem { Item = item, IsSelected = SelectedItems.Contains(item) });
                }
            }
            else
            {
                foreach (var item in Items)
                {
                    FilteredItems.Add(new SelectedItem { Item = item, IsSelected = SelectedItems.Contains(item) });
                }
            }
        }
    }

    protected override void OnInitialized()
    {
        Items?.ForEach(i => FilteredItems.Add(new SelectedItem { Item = i }));
        base.OnInitialized();
    }

    public void ToggleSelectMenu()
    {
        toggleSelectBox = !toggleSelectBox;
        ClearSearchText();
    }
    private void ClearSearchText() => FilterText = null;

    public void SelectionChanged(string item, object checkedValue)
    {
        if ((bool)checkedValue)
        {
            if (!SelectedItems.Contains(item))
            {
                SelectedItems.Add(item);
            }
        }
        else
        {
            if (SelectedItems.Contains(item))
            {
                SelectedItems.Remove(item);
            }
        }
        ButtonHoverTitleText = SelectedItems.Any() ? string.Join(",", SelectedItems.Select(x => x)) : null;
        ButtonText = SelectedItems.Any()
            ? (SelectedItems.Count == 1 ? ButtonHoverTitleText : $"{SelectedItems.Count} items selected")
            : "Nothing selected";
        SelectedItemChanged.InvokeAsync(SelectedItems);
    }

    public class SelectedItem
    {
        public string? Item { get; set; }
        public bool IsSelected { get; set; }
    }
}

给出了答案,但为了更多地解释发生了什么,下面是当您键入“o”时会发生什么。请注意 item 如何被 one 1 替换 并且没有其他变化。换句话说,Blazor 检测更新 DOM 所需的数量并应用这些更改。这通常是您想要的,但是,当您动态更新一组项目(例如您的案例)时,这种行为是不可取的。 (注意:我在下面的输入中添加了一个 firstitem 属性只是为了表明它没有得到更新)。

答案是使用 @key:

<label class="item" @key=item>

当您这样做时,只要您指定的键的 发生变化,DOM 就会更新。这将确保不仅更新 span 中的文本,而且更新输入元素。因此,根据经验,当您是列表中的 adding/removing/reordering 个元素时,请始终使用 @key。另外,我上面没有提到,但是当您重新排序项目时,@key 起着至关重要的作用,因为它确保未更改的元素得以保留。

这里是link到documentation on @key