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>
原答案
这里有几个问题:
QueryInputs
是一个 Parameter
,因此永远不应被组件内的代码修改。您最终会发现渲染器认为的值与实际值不匹配。
当父组件呈现时,它总是会导致任何传递 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();
}
我有一个 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>
原答案
这里有几个问题:
QueryInputs
是一个Parameter
,因此永远不应被组件内的代码修改。您最终会发现渲染器认为的值与实际值不匹配。当父组件呈现时,它总是会导致任何传递 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();
}