英文:
Understanding Blazor two-way binding
问题
这是您提供的代码的翻译:
Wrapper 组件代码:
我尝试创建一个包装组件,用于封装 Ant Blazor UI 框架中的 `Select` 组件。
为此,我阅读了有关双向绑定的 [Microsoft 文档][1]。
我的包装似乎按预期工作,但在进行一些日志记录时,我不理解其内部工作原理。
这是我的包装器组件的代码:
```csharp
<Select TItem="ClientTitleDto"
TItemValue="int?"
DataSource="@ClientTitleList"
@bind-Value="@ChildValue"
LabelName="@nameof(ClientTitleDto.Name)"
ValueName="@nameof(ClientTitleDto.Id)">
</Select>
@code {
[Parameter]
public int? Value { get; set; }
[Parameter]
public EventCallback<int?> ValueChanged { get; set; }
private List<ClientTitreDto> ClientTitleList { get; set; } = new ();
private int? ChildValue
{
get => Value;
set
{
Console.WriteLine($"从子组件设置,值:{value}");
ValueChanged.InvokeAsync(value);
}
}
protected override async Task OnInitializedAsync()
{
var response = await ClientTitlesApi.GetAllAsync();
ClientTitleList = response.Data;
}
protected override void OnParametersSet()
{
Console.WriteLine($"从父组件设置,值:{Value}");
}
}
父组件代码:
<MyWrapper @bind-Value="@selectedID"/>
<Button OnClick="() => selectedID = 5">从父组件更改</Button>
@code {
private int? selectedID;
}
一切都正常工作,但我不明白为什么。
当我从包装器内部的 Select
组件中选择一个值时,这是日志:
从子组件设置,值:10
从父组件设置,值:10
为什么我会从父组件中得到值?我期望值从 Select
-> Wrapper
-> Parent
传递,而不是 Select
-> Wrapper
-> Parent
-> Wrapper
。
最糟糕的情况是当我直接从父组件中设置值时:
从父组件设置,值:5
从子组件设置,值:5
从父组件设置,值:5
在我理解中,这是 Parent
-> Wrapper
-> Select
-> Wrapper
-> Parent
-> Wrapper
。
为什么会出现这种行为,以及为什么它不会导致无限循环呢?
<details>
<summary>英文:</summary>
I try to create a wrapper component around the `Select` component from Ant Blazor ui framework.
For this I read the [microsoft docs about two way binding][1].
My wrapper seems to work as intended but when I do some logging I don't understand how it works under the hood.
This is the code for my wrapper :
```csharp
<Select TItem="ClientTitleDto"
TItemValue="int?"
DataSource="@ClientTitleList"
@bind-Value="@ChildValue"
LabelName="@nameof(ClientTitleDto.Name)"
ValueName="@nameof(ClientTitleDto.Id)">
</Select>
@code {
[Parameter]
public int? Value { get; set; }
[Parameter]
public EventCallback<int?> ValueChanged { get; set; }
private List<ClientTitreDto> ClientTitleList { get; set; } = new ();
private int? ChildValue
{
get => Value;
set
{
Console.WriteLine($"Set from child, value : {value}");
ValueChanged.InvokeAsync(value);
}
}
protected override async Task OnInitializedAsync()
{
var response = await ClientTitlesApi.GetAllAsync();
ClientTitleList = response.Data;
}
protected override void OnParametersSet()
{
Console.WriteLine($"Set from parent, value : {Value}");
}
}
And this is the parent component :
<MyWrapper @bind-Value="@selectedID"/>
<Button OnClick="() => selectedID = 5">Change from parent</Button>
@code {
private int? selectedID;
}
Everything works fine but I don't understand why.
This is the log when I select a value from the Select
component inside my wrapper :
Set from child, value : 10
Set from parent, value : 10
Why do I get something from the parent? I expect the value to go from Select
-> Wrapper
-> Parent
and not Select
-> Wrapper
-> Parent
-> Wrapper
.
The worst case is when I set the value directly from the parent :
Set from parent, value : 5
Set from child, value : 5
Set from parent, value : 5
Which in my understanding do Parent
-> Wrapper
-> Select
-> Wrapper
-> Parent
-> Wrapper
Why this behaviour and how it doesn't end in a infinite loop then?
答案1
得分: 1
以下是翻译好的部分:
我已经使用标准组件重新编写了您的代码,以构建一个最小的可复制示例,以探索您报告的行为。
组件 - MySelect.razor
```csharp
@using System.Diagnostics;
<InputSelect class="form-select" @bind-Value=ChildValue >
@if(ChildValue is null)
{
<option disabled selected value=""> -- 选择一个值 -- </option>
}
@foreach(var option in options)
{
<option value="@option.Value">@option.Description</option>
}
</InputSelect>
@code {
[Parameter] public int? Value { get; set; }
[Parameter] public EventCallback<int?> ValueChanged { get; set; }
private int? ChildValue
{
get => Value;
set
{
Debug.WriteLine($"从子组件设置,值:{value}");
ValueChanged.InvokeAsync(value);
}
}
protected override async Task OnInitializedAsync()
{
// 虚拟的异步行为
await Task.Delay(100);
}
protected override void OnParametersSet()
{
Debug.WriteLine($"从父组件设置,值:{Value}");
}
public IEnumerable<OptionList> options = new List<OptionList>
{
new OptionList(1, "英国"),
new OptionList(2, "法国"),
new OptionList(3, "葡萄牙"),
new OptionList(4, "西班牙"),
new OptionList(5, "意大利"),
};
public record OptionList(int Value, string Description);
}
测试页面:
@page "/"
<PageTitle>Index</PageTitle>
<h1>你好,世界!</h1>
这是父组件:
<MySelect @bind-Value="@selectedID"/>
<div class="m-2 p-2">
<button class="btn btn-primary" @onclick="() => selectedID = 5">从父组件更改</button>
</div>
@code {
private int? selectedID;
}
- 在启动时,
OnParametersSet
在组件渲染时被调用,Value
设置为null
。
从父组件设置,值:
- 选择“法国”。
Setter 被调用,将值 2 传回父组件,并记录第一条消息。这将在父组件中将 selectedID
设置为 2。父组件因为回调是一个 UI 事件而重新渲染。MySelect
上的绑定值已更改,因此重新渲染 MySelect
。OnParametersSet
在 MySelect
上运行,并记录第二条消息。
从子组件设置,值: 2
从父组件设置,值: 2
- 单击父组件按钮。
这将在父组件中将 selectedID
设置为 5。父组件因为按钮点击是一个 UI 事件而重新渲染。MySelect
上的绑定值已更改,因此重新渲染 MySelect
。OnParametersSet
在 MySelect
上运行,并记录第二条消息。
从父组件设置,值: 5
这一切都是预期的行为。您报告的行为很可能是由 Ant Design 控件引起的,但我不理解为什么,这也不是我所期望的。这里回答问题的大多数人不是第三方组件专家,所以您可能需要向 Ant Design 提问。
英文:
I've re-written your code using standard components to build a minimal reproducible example to explore the behaviour you report.
The Component - MySelect.razor
@using System.Diagnostics;
<InputSelect class="form-select" @bind-Value=ChildValue >
@if(ChildValue is null)
{
<option disabled selected value=""> -- Select a Value -- </option>
}
@foreach(var option in options)
{
<option value="@option.Value">@option.Description</option>
}
</InputSelect>
@code {
[Parameter] public int? Value { get; set; }
[Parameter] public EventCallback<int?> ValueChanged { get; set; }
private int? ChildValue
{
get => Value;
set
{
Debug.WriteLine($"Set from child, value : {value}");
ValueChanged.InvokeAsync(value);
}
}
protected override async Task OnInitializedAsync()
{
// dummy async behaviour
await Task.Delay(100);
}
protected override void OnParametersSet()
{
Debug.WriteLine($"Set from parent, value : {Value}");
}
public IEnumerable<OptionList> options = new List<OptionList>
{
new OptionList(1, "UK"),
new OptionList(2, "France"),
new OptionList(3, "Portugal"),
new OptionList(4, "Spain"),
new OptionList(5, "Italy"),
};
public record OptionList(int Value, string Description);
}
And the test page:
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
And this is the parent component :
<MySelect @bind-Value="@selectedID"/>
<div class="m-2 p-2">
<button class="btn btn-primary" @onclick="() => selectedID = 5">Change from parent</button>
</div>
@code {
private int? selectedID;
}
- At startup
OnParametersSet
is called as the component renders withValue
set tonull
.
Set from parent, value :
- Select
France
.
The setter is called passing a value of 2 back to the parent - and logs the first message.
This sets selectedID
to 2 in the parent.
The parent renders as the Callback is a UI event.
The bind value on MySelect
has changed so the render also renders MySelect
OnParametersSet
is run on MySelect
- and logs the second message.
Set from child, value : 2
Set from parent, value : 2
- Click the parent button.
This sets selectedID
to 2 in the parent.
The parent renders as the button click is a UI event.
The bind value on MySelect
has changed so the render also renders MySelect
OnParametersSet
is run on MySelect
- and logs the second message.
Set from parent, value : 5
This is all as expected. The behaviour you report may well be caused by the Ant control, but I don't understand why, and it's not what I would expect? Most people who answer questions on here are not third party component experts, so you'll probably need to ask Ant Design why.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论