理解Blazor双向绑定

huangapple go评论104阅读模式
英文:

Understanding Blazor two-way binding

问题

这是您提供的代码的翻译:

Wrapper 组件代码:

  1. 我尝试创建一个包装组件,用于封装 Ant Blazor UI 框架中的 `Select` 组件。
  2. 为此,我阅读了有关双向绑定的 [Microsoft 文档][1]
  3. 我的包装似乎按预期工作,但在进行一些日志记录时,我不理解其内部工作原理。
  4. 这是我的包装器组件的代码:
  5. ```csharp
  6. <Select TItem="ClientTitleDto"
  7. TItemValue="int?"
  8. DataSource="@ClientTitleList&quot
  9. @bind-Value="@ChildValue&quot
  10. LabelName="@nameof(ClientTitleDto.Name)&quot
  11. ValueName="@nameof(ClientTitleDto.Id)">
  12. </Select>
  13. @code {
  14. [Parameter]
  15. public int? Value { get; set; }
  16. [Parameter]
  17. public EventCallback<int?> ValueChanged { get; set; }
  18. private List<ClientTitreDto> ClientTitleList { get; set; } = new ();
  19. private int? ChildValue
  20. {
  21. get => Value;
  22. set
  23. {
  24. Console.WriteLine($"从子组件设置,值:{value}");
  25. ValueChanged.InvokeAsync(value);
  26. }
  27. }
  28. protected override async Task OnInitializedAsync()
  29. {
  30. var response = await ClientTitlesApi.GetAllAsync();
  31. ClientTitleList = response.Data;
  32. }
  33. protected override void OnParametersSet()
  34. {
  35. Console.WriteLine($"从父组件设置,值:{Value}");
  36. }
  37. }

父组件代码:

  1. <MyWrapper @bind-Value="@selectedID"/>
  2. <Button OnClick="() => selectedID = 5">从父组件更改</Button>
  3. @code {
  4. private int? selectedID;
  5. }

一切都正常工作,但我不明白为什么。

当我从包装器内部的 Select 组件中选择一个值时,这是日志:

  1. 从子组件设置,值:10
  2. 从父组件设置,值:10

为什么我会从父组件中得到值?我期望值从 Select -> Wrapper -> Parent 传递,而不是 Select -> Wrapper -> Parent -> Wrapper

最糟糕的情况是当我直接从父组件中设置值时:

  1. 从父组件设置,值:5
  2. 从子组件设置,值:5
  3. 从父组件设置,值:5

在我理解中,这是 Parent -> Wrapper -> Select -> Wrapper -> Parent -> Wrapper

为什么会出现这种行为,以及为什么它不会导致无限循环呢?

  1. <details>
  2. <summary>英文:</summary>
  3. I try to create a wrapper component around the `Select` component from Ant Blazor ui framework.
  4. For this I read the [microsoft docs about two way binding][1].
  5. My wrapper seems to work as intended but when I do some logging I don&#39;t understand how it works under the hood.
  6. This is the code for my wrapper :
  7. ```csharp
  8. &lt;Select TItem=&quot;ClientTitleDto&quot;
  9. TItemValue=&quot;int?&quot;
  10. DataSource=&quot;@ClientTitleList&quot;
  11. @bind-Value=&quot;@ChildValue&quot;
  12. LabelName=&quot;@nameof(ClientTitleDto.Name)&quot;
  13. ValueName=&quot;@nameof(ClientTitleDto.Id)&quot;&gt;
  14. &lt;/Select&gt;
  15. @code {
  16. [Parameter]
  17. public int? Value { get; set; }
  18. [Parameter]
  19. public EventCallback&lt;int?&gt; ValueChanged { get; set; }
  20. private List&lt;ClientTitreDto&gt; ClientTitleList { get; set; } = new ();
  21. private int? ChildValue
  22. {
  23. get =&gt; Value;
  24. set
  25. {
  26. Console.WriteLine($&quot;Set from child, value : {value}&quot;);
  27. ValueChanged.InvokeAsync(value);
  28. }
  29. }
  30. protected override async Task OnInitializedAsync()
  31. {
  32. var response = await ClientTitlesApi.GetAllAsync();
  33. ClientTitleList = response.Data;
  34. }
  35. protected override void OnParametersSet()
  36. {
  37. Console.WriteLine($&quot;Set from parent, value : {Value}&quot;);
  38. }
  39. }

And this is the parent component :

  1. &lt;MyWrapper @bind-Value=&quot;@selectedID&quot;/&gt;
  2. &lt;Button OnClick=&quot;() =&gt; selectedID = 5&quot;&gt;Change from parent&lt;/Button&gt;
  3. @code {
  4. private int? selectedID;
  5. }

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 :

  1. Set from child, value : 10
  2. 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 :

  1. Set from parent, value : 5
  2. Set from child, value : 5
  3. 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

以下是翻译好的部分:

  1. 我已经使用标准组件重新编写了您的代码,以构建一个最小的可复制示例,以探索您报告的行为。
  2. 组件 - MySelect.razor
  3. ```csharp
  4. @using System.Diagnostics;
  5. &lt;InputSelect class=&quot;form-select&quot; @bind-Value=ChildValue &gt;
  6. @if(ChildValue is null)
  7. {
  8. &lt;option disabled selected value=&quot;&quot;&gt; -- 选择一个值 -- &lt;/option&gt;
  9. }
  10. @foreach(var option in options)
  11. {
  12. &lt;option value=&quot;@option.Value&quot;&gt;@option.Description&lt;/option&gt;
  13. }
  14. &lt;/InputSelect&gt;
  15. @code {
  16. [Parameter] public int? Value { get; set; }
  17. [Parameter] public EventCallback&lt;int?&gt; ValueChanged { get; set; }
  18. private int? ChildValue
  19. {
  20. get =&gt; Value;
  21. set
  22. {
  23. Debug.WriteLine($&quot;从子组件设置,值:{value}&quot;);
  24. ValueChanged.InvokeAsync(value);
  25. }
  26. }
  27. protected override async Task OnInitializedAsync()
  28. {
  29. // 虚拟的异步行为
  30. await Task.Delay(100);
  31. }
  32. protected override void OnParametersSet()
  33. {
  34. Debug.WriteLine($&quot;从父组件设置,值:{Value}&quot;);
  35. }
  36. public IEnumerable&lt;OptionList&gt; options = new List&lt;OptionList&gt
  37. {
  38. new OptionList(1, &quot;英国&quot;),
  39. new OptionList(2, &quot;法国&quot;),
  40. new OptionList(3, &quot;葡萄牙&quot;),
  41. new OptionList(4, &quot;西班牙&quot;),
  42. new OptionList(5, &quot;意大利&quot;),
  43. };
  44. public record OptionList(int Value, string Description);
  45. }

测试页面:

  1. @page &quot;/&quot;
  2. &lt;PageTitle&gt;Index&lt;/PageTitle&gt;
  3. &lt;h1&gt;你好,世界!&lt;/h1&gt;
  4. 这是父组件:
  5. &lt;MySelect @bind-Value=&quot;@selectedID&quot;/&gt;
  6. &lt;div class=&quot;m-2 p-2&quot;&gt;
  7. &lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;() =&gt; selectedID = 5&quot;&gt;从父组件更改&lt;/button&gt;
  8. &lt;/div&gt;
  9. @code {
  10. private int? selectedID;
  11. }
  1. 在启动时,OnParametersSet 在组件渲染时被调用,Value 设置为 null
  1. 从父组件设置,值:
  1. 选择“法国”。

Setter 被调用,将值 2 传回父组件,并记录第一条消息。这将在父组件中将 selectedID 设置为 2。父组件因为回调是一个 UI 事件而重新渲染。MySelect 上的绑定值已更改,因此重新渲染 MySelectOnParametersSetMySelect 上运行,并记录第二条消息。

  1. 从子组件设置,值: 2
  2. 从父组件设置,值: 2
  1. 单击父组件按钮。

这将在父组件中将 selectedID 设置为 5。父组件因为按钮点击是一个 UI 事件而重新渲染。MySelect 上的绑定值已更改,因此重新渲染 MySelectOnParametersSetMySelect 上运行,并记录第二条消息。

  1. 从父组件设置,值: 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

  1. @using System.Diagnostics;
  2. &lt;InputSelect class=&quot;form-select&quot; @bind-Value=ChildValue &gt;
  3. @if(ChildValue is null)
  4. {
  5. &lt;option disabled selected value=&quot;&quot;&gt; -- Select a Value -- &lt;/option&gt;
  6. }
  7. @foreach(var option in options)
  8. {
  9. &lt;option value=&quot;@option.Value&quot;&gt;@option.Description&lt;/option&gt;
  10. }
  11. &lt;/InputSelect&gt;
  12. @code {
  13. [Parameter] public int? Value { get; set; }
  14. [Parameter] public EventCallback&lt;int?&gt; ValueChanged { get; set; }
  15. private int? ChildValue
  16. {
  17. get =&gt; Value;
  18. set
  19. {
  20. Debug.WriteLine($&quot;Set from child, value : {value}&quot;);
  21. ValueChanged.InvokeAsync(value);
  22. }
  23. }
  24. protected override async Task OnInitializedAsync()
  25. {
  26. // dummy async behaviour
  27. await Task.Delay(100);
  28. }
  29. protected override void OnParametersSet()
  30. {
  31. Debug.WriteLine($&quot;Set from parent, value : {Value}&quot;);
  32. }
  33. public IEnumerable&lt;OptionList&gt; options = new List&lt;OptionList&gt;
  34. {
  35. new OptionList(1, &quot;UK&quot;),
  36. new OptionList(2, &quot;France&quot;),
  37. new OptionList(3, &quot;Portugal&quot;),
  38. new OptionList(4, &quot;Spain&quot;),
  39. new OptionList(5, &quot;Italy&quot;),
  40. };
  41. public record OptionList(int Value, string Description);
  42. }

And the test page:

  1. @page &quot;/&quot;
  2. &lt;PageTitle&gt;Index&lt;/PageTitle&gt;
  3. &lt;h1&gt;Hello, world!&lt;/h1&gt;
  4. And this is the parent component :
  5. &lt;MySelect @bind-Value=&quot;@selectedID&quot;/&gt;
  6. &lt;div class=&quot;m-2 p-2&quot;&gt;
  7. &lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;() =&gt; selectedID = 5&quot;&gt;Change from parent&lt;/button&gt;
  8. &lt;/div&gt;
  9. @code {
  10. private int? selectedID;
  11. }
  1. At startup OnParametersSet is called as the component renders with Value set to null.
  1. Set from parent, value :
  1. 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.

  1. Set from child, value : 2
  2. Set from parent, value : 2
  1. 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.

  1. 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.

huangapple
  • 本文由 发表于 2023年2月23日 23:51:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/75547210.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定