何时在 Blazor 中使用 ValueChanged 和 ValueExpression?

Posted

技术标签:

【中文标题】何时在 Blazor 中使用 ValueChanged 和 ValueExpression?【英文标题】:When to use ValueChanged and ValueExpression in Blazor? 【发布时间】:2020-03-12 16:42:56 【问题描述】:

我在一些库(MatBlazor、Telerik)中看到了这种具有ValueChangedValueExpression 属性的常见模式,这让我很困惑。

两者有什么区别?什么时候使用?

【问题讨论】:

ValueChanged 是双向数据绑定事件,ValueExpression 是绑定属性的 Linq 表达式。 @enet 因为我想知道它是如何工作的以及它的用途是什么? 好的,没问题... 【参考方案1】:

我想为ValueChangedValueExpression 添加一些用例,

首先,正如 enet 所说,这些属性更像是三个属性,您有 FooFooChangedFooExpression,它用于双向数据绑定,例如@bind-Foo="SomeProperty".

要创建具有可与@bind- 一起使用的属性的自定义组件,您需要提供这三个属性(仅提供FooFooChanged 也可以)作为[Parameter] 并调用FooChanged 当自定义组件中的属性发生变化。

例如来自网络

[Parameter]
public TValue Foo

    get => text
    set
    
        if (text != value) 
            text = value;
            if (FooChanged.HasDelegate)
            
                FooChanged.InvokeAsync(value);
            
        
    


[Parameter]
public EventCallback<TValue> FooChanged  get; set; 

[Parameter]
public Expression<Func<TValue>> FooExpression  get; set;   

添加@bind-Foo 与传递ValueValueChanged 相同,唯一的区别是@bind- 只会设置属性,但如果添加自己的ValueChanged,则可以做任何事情您想要(验证、更改要设置的值等)。

用例

1 - 创建一个用@bind- 包装另一个组件的组件

如果您有一个组件已经有@bind-Foo,并且您想在此基础上创建一个组件并仍然作为参数传递@bind-Foo,您可以只有一个属性并传递给@bind-Foo,您需要将属性传递给FooFooChanged 和/或FooExpression

例如

CustomInputWrapper.razor

<div>
    <p>My custom input wrapper</p>
    @* If you pass @bind-Value it won't work*@
    @* You need to pass the properties that are used in the bind*@
    <InputText Text="@Value" TextChanged="@ValueChanged" TextExpression="@ValueExpression" />
</div>

@code     
    [Parameter]
    public virtual string Value  get; set; 

    [Parameter]
    public EventCallback<string > ValueChanged  get; set; 

    [Parameter]
    public Expression<Func<string >> ValueExpression  get; set;         

如果您正在制作大量自定义组件或不想直接使用某些第三方组件,这种包装另一个组件的情况会发生很多。

我的项目示例:在我的项目中,我使用的是 MatBlazor 和 Telerik,但并不是两个库中的所有组件都是完全稳定的,所以我为所有组件创建了一个包装器,有一天,当其中一个库是完全稳定的,我将改为只使用一个库。这样做可以让我拥有我的自定义组件,如果我想更改一个,我只需在我的自定义组件中更改一件事并更改整个应用程序。

2 - 添加默认值

如果您想在 自定义组件 中使用默认值,您“可以”将默认值传递给属性。

[Parameter]
public virtual DateTime Value  get; set;  = new DateTime(/* some default value*/);

但是如果你在一个表单中使用这个组件,就会有很大的问题。

为什么?因为您只会更改组件内部的值,但如果在 @bind-Value 中传递了属性,则不会更改。

要添加此默认值并使其在双向数据绑定中起作用,您需要调用ValueChanged 并传递默认值。这将使您的组件具有默认值,并且还将更改 @bind-Value 中的任何属性以具有默认值。

例如

// Lifecycle after all parameters are set
protected override void OnParametersSet()

    // Check if the ValueChanged is set
    if (ValueChanged.HasDelegate)
    
        ValueChanged.InvokeAsync(DateTime.Now);
    

3 - 你真正需要的用例FooExpression

当你有一个可以为空的类型时,例如int?,有时,当值为null时,它无法知道它的类型,所以你需要传递FooExpression,这样它才能通过反射得到类型。这是您需要使用的example。


如果您正在制作自定义组件并且必须使用绑定属性或更改绑定的工作方式,这些属性的用例将被更多地使用。

如果您只使用已经制作的组件,那么您将不得不使用它的情况很少见。

【讨论】:

【参考方案2】:

实际上,您已经忘记了此模式的第三个元素:Value。这种“三位一体”的属性经常用于组件双向数据绑定。值得注意的是,这些属性在内置 Blazor 表单组件中使用,例如 &lt;InputText&gt;

我们来看一个例子:

<InputText @bind-Value="employee.FirstName" />

    Value是以@bind-Value="model.PropertyName"的形式提供的属性。

    ValueChangedEventCallback&lt;TValue&gt; 类型。它代表更新绑定值的回调。如您所见,我们在上面的示例中没有使用它——没有必要。编译器知道它的工作并会处理这个问题,这意味着它会添加一个 EventCallback “delegate”,并在你背后进行所有必要的设置。

    ValueExpression 最后是指一个标识绑定值的表达式。它由编译器自动创建,您很少需要设置它。

现在让我们将上面的代码与下面的代码进行比较。 下面的示例在父组件和子组件之间创建双向数据绑定。但是,我们不会使用标准的“三位一体”(ValueValueChangedValueExpression),而是为自己复制底层模式:

ParentComponent.razor:

<ChildComponent @bind-Text="FirstName" />

@code 
    [Parameter]
    public string FirstName  get; set; 

ChildComponent.razor:

<input @bind="Text" />

@code 
    private string text;

    [Parameter]
    public string Text
    
        get  return text; 
        set
        
            if (text != value) 
                text = value;
                if (TextChanged.HasDelegate)
                
                    TextChanged.InvokeAsync(value);
                
            
        
    

    [Parameter]
    public EventCallback<string> TextChanged  get; set; 

内置的&lt;InputText&gt;和我们自定义的&lt;ChildComponent&gt;基本一致!


回答你的其他问题...

我什么时候在 Blazor 中使用 ValueChangedValueExpression?我正在为另一个库的输入创建一个包装器,这是使用这个三位一体的情况吗?

如上所述,ValueChangedValueExpression 是在 Blazor 的内置组件中定义的属性,大多数情况下您不需要直接使用它们。

再次查看我在上面定义的两个组件:&lt;ParentComponent&gt;&lt;ChildComponent&gt;。将TextTextChanged 更改为ValueValueChanged,我的组件仍然有效并且可以正常工作。 唯一的区别是命名。 我在&lt;ChildComponent&gt; 中做什么?我定义了一个名为Text(代表Value)的参数属性。由于我想在父子组件之间启用双向数据绑定,我还需要定义一个参数属性,这里称为TextChanged(代表ValueChanged)。 Text 转到 TextChangedValue 转到 ValueChangedYear 转到 YearChanged。命名只是惯例。重点是你必须定义一个属性和一个与该属性数据类型相同的EventCallback

在父组件中我提供如下属性:

&lt;ChildComponent @bind-Text="NameOfAPropertyDefinedInTheParentComponent" /&gt;&lt;ChildComponent @bind-Value="NameOfAPropertyDefinedInTheParentComponent" /&gt;&lt;ChildComponent @bind-Year="NameOfAPropertyDefinedInTheParentComponent" /&gt;

在我上面的组件中,还有一些代码,例如在子组件中,调用TextChanged 委托以将值传递回父组件;这正是ValueChanged 委托在定义它的组件中所做的事情。但是您作为用户不必使用它。看看我的组件……它们工作得很好。没必要摸。如果您作为我的组件的用户想要对其进行子类化,那么您需要知道自己在做什么以及如何正确地对 Blazor 组件进行子类化。但是我的组件(这里部分展示)相对简单。

假设您想创建一个基于&lt;InputText&gt; 的密码输入,这不仅可行而且非常简单。在这种情况下,您只需要更改 &lt;InputText&gt; 组件的外观,以便显示星号符号而不是普通文本。组件的其余部分保持不变。您不需要处理事件等。当然,这并不意味着组件作者永远不需要从他的代码中的某个地方调用EventCallback。也就是说,在使用&lt;InputText&gt; 组件时,我从来没有充分的理由触发ValueChanged 委托。而且我只需要提供一次ValueExpression,因为编译器无法识别绑定值。 (我去找找,如果找到我就贴在这里……)

【讨论】:

什么时候在 Blazor 中使用 ValueChangedValueExpression?我正在为另一个库的输入创建一个包装器,这是使用这个三位一体的情况吗? 很好的答案,这间接解释了这是一个 Blazor 约定,MatBlazor 和 Telerik 遵循框架设置的约定。 @Ed Charbeneau,感谢您的夸奖...我受宠若惊,因为我从您的工作中学到了很多... 顺便提一下,提到 Telerik,我现在记得这是我唯一看到处理和解释 ValueExpression 的样本的地方。比你。 @Vencovsky 据我了解,每当你实现使用双向绑定的组件时,你应该使用ValueValueChanged。如果您使用依赖于识别绑定属性的功能(例如实现验证)。然后你还需要使用ValueExpression。例如,您可以使用FieldIdentifier.Create(ValueExpression) 创建一个FieldIdentifier。 - 希望这会有所帮助。

以上是关于何时在 Blazor 中使用 ValueChanged 和 ValueExpression?的主要内容,如果未能解决你的问题,请参考以下文章

我应该何时调用 StateHasChanged 以及 Blazor 何时自动拦截某些更改?

如何在 Blazor 中使用 TagHelpers?

使用 Blazor 开发内部后台:了解 Blazor 组件

在 blazor 应用程序之外使用 blazor 组件(例如,knockoutjs)

在 Blazor 中使用带有 AddDbContextFactory 的标识

如何使用 Blazor 上传文件?