Blazor class 上的双向组件参数绑定?

Two-Way Component Parameter Binding on a class in Blazor?

我有一个 class MealsQueryInputs,我想将其用作具有双向绑定功能的组件参数。

我能找到的所有演示和示例代码都使用内置原始类型,而不是 class。我可以让 MS demos 工作,但我无法绑定到 class 工作。甚至可以这样做吗?

我的组件FilterSortOptions.razor:

using WhatIsForDinner.Shared.Models

<MudCheckBox Checked="@QueryInputs.Favorite" 
             Color="Color.Inherit" 
             CheckedIcon="@Icons.Material.Filled.Favorite" 
             UncheckedIcon="@Icons.Material.Filled.FavoriteBorder"
             T="bool"/>
<MudRating SelectedValue="@QueryInputs.Rating"/>
<MudButton OnClick="@(async () => await OnPropertyChanged())">Apply</MudButton>

@code {
    [Parameter]
    public MealsQueryInputs QueryInputs { get; set; }

    [Parameter]
    public EventCallback<MealsQueryInputs> QueryInputsChanged { get; set; }

    private async Task OnPropertyChanged()
    {
        await QueryInputsChanged.InvokeAsync(QueryInputs);
    }
}

更新答案

首先,如果您使用一个对象,那么您就是在传递对同一对象的引用。因此,当您更新 sub-component 中的对象时,您更新的是父级正在使用的同一对象。您不需要在回调中传回该对象,除非您创建它的新副本。

其次,您没有将泥浆控件绑定到对象。

让我们看看你的代码:

<MudCheckBox Checked="@QueryInputs.Favorite" 
             Color="Color.Inherit" 
             CheckedIcon="@Icons.Material.Filled.Favorite" 
             UncheckedIcon="@Icons.Material.Filled.FavoriteBorder"
             T="bool"/>

Checked="@QueryInputs.Favorite" 没有将控件绑定到字段。它只是设置初始值。

我认为(我不使用 Mudblazor,它与标准的 Blazor 表单控件有点不同)您需要这样做:

<MudCheckBox @bind-Checked="@QueryInputs.Favorite"></MudCheckBox>

MudRating也是如此。

    <MudRating @bind-SelectedValue="@QueryInputs.Rating" />

然后按钮:

<MudButton OnClick="@(async () => await OnPropertyChanged())">Apply</MudButton>

可以简化成这样。您正在将异步方法包装在异步方法中。

<MudButton OnClick="OnPropertyChanged">Apply</MudButton>
// or
<MudButton OnClick="() => OnPropertyChanged()">Apply</MudButton>

原答案

这里有几个问题:

  1. QueryInputs 是一个 Parameter,因此永远不应被组件内的代码修改。您最终会发现渲染器认为的值与实际值不匹配。

  2. 当父组件呈现时,它总是会导致任何传递 class 作为参数的组件的 re-render。渲染器无法判断 class 是否已被修改,因此它应用了笨拙的解决方案 - 在组件上调用 SetParametersAsync

一种解决方案是使用视图服务来保存数据和事件以通知更改。一个版本的真相!搜索“Blazor Notification Pattern”以获取有关如何实现此功能的示例。如果你找不到你想要的,我会 post 一些代码。

正如 MrC 所说,您应该避免直接绑定到作为参数提供的数据。

这是一个简单的工作示例(不是 MudBlazor)来展示这个概念

https://blazorrepl.telerik.com/QQEnQjaO54LY3MYK35

你绑定到一个本地variable/property,尽量不要直接修改传入的数据

我的组件


<h1>MyComponent</h1>

<label for="choice">Choose</label>
<input id="choice" type="checkbox" @bind-value=localValue  />

@code
{
    bool localValue
    {
        get => Data.SomeChoice;
        set {
            if (value != localValue)
            {
                localData = Data with { SomeChoice = value };
                InvokeAsync(ValueChanged);
            }
        }
    }
    ComplexObject localData;
    [Parameter] public ComplexObject Data { get; set; }
    [Parameter] public EventCallback<ComplexObject> DataChanged { get; set; }

    Task ValueChanged() => DataChanged.InvokeAsync(localData);
}

复杂对象

public record ComplexObject(bool SomeChoice, string SomeText);

主要

@code
{
    ComplexObject data = new(false,"");
}

<MyComponent @bind-Data=data />

You have chosen @data.SomeChoice

这是将 class 对象绑定到自定义剃须刀组件的方法

这是 FilterSortOptions 组件

<div>
    <label>Rating:</label>
    <input type="text" value=@QueryInputs.Rating @oninput=@(val=> {
           QueryInputs.Rating=val.Value.ToString();
           QueryInputsChanged.InvokeAsync(QueryInputs);
           }) />
</div>

<div>
    <label>Favourite:</label>
    <input type="checkbox" value=@QueryInputs.Rating @onchange=@(val=> {
           QueryInputs.Favourite=(bool)val.Value;
           QueryInputsChanged.InvokeAsync(QueryInputs);
           }) />
</div>

    @code {
    
        [Parameter]
        public MealsQueryInputs QueryInputs { get; set; }
    
        [Parameter]
        public EventCallback<MealsQueryInputs> QueryInputsChanged { get; set; }
    
    }

这是要绑定的模型,为简单起见评级是字符串类型

public class MealsQueryInputs
{
    public bool Favourite { get; set; } = false;
    public string Rating { get; set; } = "0";
}

这是剃须刀页面

<h3>Rating: @QueryInputs.Rating</h3>
<h3>Favourite: @QueryInputs.Favourite</h3>

<FilterSortOptions @bind-QueryInputs=@QueryInputs></FilterSortOptions>

@code {
    public MealsQueryInputs QueryInputs = new();
}