理解Blazor双向绑定

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

Understanding Blazor two-way binding

问题

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

Wrapper 组件代码:

我尝试创建一个包装组件,用于封装 Ant Blazor UI 框架中的 `Select` 组件。

为此,我阅读了有关双向绑定的 [Microsoft 文档][1]

我的包装似乎按预期工作,但在进行一些日志记录时,我不理解其内部工作原理。

这是我的包装器组件的代码:
```csharp
<Select TItem="ClientTitleDto"
        TItemValue="int?"
        DataSource="@ClientTitleList&quot
        @bind-Value="@ChildValue&quot
        LabelName="@nameof(ClientTitleDto.Name)&quot
        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&#39;t understand how it works under the hood.

This is the code for my wrapper :
```csharp
&lt;Select TItem=&quot;ClientTitleDto&quot;
        TItemValue=&quot;int?&quot;
        DataSource=&quot;@ClientTitleList&quot;
        @bind-Value=&quot;@ChildValue&quot;
        LabelName=&quot;@nameof(ClientTitleDto.Name)&quot;
        ValueName=&quot;@nameof(ClientTitleDto.Id)&quot;&gt;
&lt;/Select&gt;

@code {
    [Parameter]
    public int? Value { get; set; }
    [Parameter]
    public EventCallback&lt;int?&gt; ValueChanged { get; set; }

    private List&lt;ClientTitreDto&gt; ClientTitleList { get; set; } = new ();
    
    private int? ChildValue
    {
        get =&gt; Value;
        set
        {
            Console.WriteLine($&quot;Set from child, value : {value}&quot;);
            ValueChanged.InvokeAsync(value);
        }
    }

    protected override async Task OnInitializedAsync()
    {
        var response = await ClientTitlesApi.GetAllAsync();
        ClientTitleList = response.Data;
    }

    protected override void OnParametersSet()
    {
        Console.WriteLine($&quot;Set from parent, value : {Value}&quot;);
    }
}

And this is the parent component :

&lt;MyWrapper @bind-Value=&quot;@selectedID&quot;/&gt;

&lt;Button OnClick=&quot;() =&gt; selectedID = 5&quot;&gt;Change from parent&lt;/Button&gt;

@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;

&lt;InputSelect class=&quot;form-select&quot; @bind-Value=ChildValue &gt;
    @if(ChildValue is null)
    {
        &lt;option disabled selected value=&quot;&quot;&gt; -- 选择一个值 -- &lt;/option&gt;
    }
    @foreach(var option in options)
    {
        &lt;option value=&quot;@option.Value&quot;&gt;@option.Description&lt;/option&gt;
    }
&lt;/InputSelect&gt;

@code {
    [Parameter] public int? Value { get; set; }
    [Parameter] public EventCallback&lt;int?&gt; ValueChanged { get; set; }

    private int? ChildValue
    {
        get =&gt; Value;
        set
        {
            Debug.WriteLine($&quot;从子组件设置,值:{value}&quot;);
            ValueChanged.InvokeAsync(value);
        }
    }

    protected override async Task OnInitializedAsync()
    {
        // 虚拟的异步行为
        await Task.Delay(100);
    }

    protected override void OnParametersSet()
    {
        Debug.WriteLine($&quot;从父组件设置,值:{Value}&quot;);
    }

    public IEnumerable&lt;OptionList&gt; options = new List&lt;OptionList&gt
    {
        new OptionList(1, &quot;英国&quot;),
        new OptionList(2, &quot;法国&quot;),
        new OptionList(3, &quot;葡萄牙&quot;),
        new OptionList(4, &quot;西班牙&quot;),
        new OptionList(5, &quot;意大利&quot;),
    };

    public record OptionList(int Value, string Description);
}

测试页面:

@page &quot;/&quot;

&lt;PageTitle&gt;Index&lt;/PageTitle&gt;

&lt;h1&gt;你好,世界!&lt;/h1&gt;

这是父组件:

&lt;MySelect @bind-Value=&quot;@selectedID&quot;/&gt;

&lt;div class=&quot;m-2 p-2&quot;&gt;
    &lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;() =&gt; selectedID = 5&quot;&gt;从父组件更改&lt;/button&gt;
&lt;/div&gt;

@code {
    private int? selectedID;
}
  1. 在启动时,OnParametersSet 在组件渲染时被调用,Value 设置为 null
从父组件设置,值: 
  1. 选择“法国”。

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

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

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

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

&lt;InputSelect class=&quot;form-select&quot; @bind-Value=ChildValue &gt;
    @if(ChildValue is null)
    {
        &lt;option disabled selected value=&quot;&quot;&gt; -- Select a Value -- &lt;/option&gt;
    }
    @foreach(var option in options)
    {
        &lt;option value=&quot;@option.Value&quot;&gt;@option.Description&lt;/option&gt;
    }
&lt;/InputSelect&gt;

@code {
    [Parameter] public int? Value { get; set; }
    [Parameter] public EventCallback&lt;int?&gt; ValueChanged { get; set; }

    private int? ChildValue
    {
        get =&gt; Value;
        set
        {
            Debug.WriteLine($&quot;Set from child, value : {value}&quot;);
            ValueChanged.InvokeAsync(value);
        }
    }

    protected override async Task OnInitializedAsync()
    {
        // dummy async behaviour
        await Task.Delay(100);
    }

    protected override void OnParametersSet()
    {
        Debug.WriteLine($&quot;Set from parent, value : {Value}&quot;);
    }

    public IEnumerable&lt;OptionList&gt; options = new List&lt;OptionList&gt;
    {
        new OptionList(1, &quot;UK&quot;),
        new OptionList(2, &quot;France&quot;),
        new OptionList(3, &quot;Portugal&quot;),
        new OptionList(4, &quot;Spain&quot;),
        new OptionList(5, &quot;Italy&quot;),
    };

    public record OptionList(int Value, string Description);
}

And the test page:

@page &quot;/&quot;

&lt;PageTitle&gt;Index&lt;/PageTitle&gt;

&lt;h1&gt;Hello, world!&lt;/h1&gt;

And this is the parent component :

&lt;MySelect @bind-Value=&quot;@selectedID&quot;/&gt;

&lt;div class=&quot;m-2 p-2&quot;&gt;
    &lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;() =&gt; selectedID = 5&quot;&gt;Change from parent&lt;/button&gt;
&lt;/div&gt;

@code {
    private int? selectedID;
}
  1. At startup OnParametersSet is called as the component renders with Value set to null.
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.

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

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:

确定