14

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

两者有什么区别?以及何时使用它?

4

2 回答 2

17

我想为ValueChangedand添加一些用例ValueExpression

首先,正如 enet 所说,这些属性更像是您拥有的属性的三位一体FooFooChanged并且FooExpression它用于双向数据绑定,例如@bind-Foo="SomeProperty".

要创建具有可与您一起使用的属性的自定义组件,@bind-您需要提供这 3 个属性(仅提供Foo并且FooChanged也可以工作),并在自定义组件中的属性更改时[Parameter]调用。FooChanged

例如来自enet

[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将与传递Value和相同ValueChanged,唯一的区别是@bind-只会设置属性,但如果添加自己的ValueChanged,则可以做任何您想做的事情(验证、更改要设置的值等)。

用例

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

如果您有一个已经有 a 的组件,@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它才能通过反射获取类型。这是您需要使用它的示例。


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

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

于 2020-04-01T12:27:49.920 回答
14

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

让我们看一个例子:

<InputText @bind-Value="employee.FirstName" />
  1. Value是以 的形式提供的属性@bind-Value="model.PropertyName"

  2. ValueChanged是类型EventCallback<TValue>。它代表更新绑定值的回调。如您所见,我们在上面的示例中没有使用它——没有必要。编译器知道它的工作并且会处理它,这意味着它会EventCallback在你背后添加一个包含所有必要设置的“委托”。

  3. ValueExpression,最后,指的是标识绑定值的表达式。它是由编译器自动创建的,您很少(如果有的话)必须设置它。

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

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

内置的<InputText>和我们自定义<ChildComponent>的基本一样!


要回答你的另一个问题...

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

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

再看看我上面定义的两个组件:<ParentComponent><ChildComponent>. 将and更改为Textand ,我的组件仍然有效并且可以正常工作。唯一的区别在于命名。我在里面做什么?我定义了一个名为( 代表) 的参数属性。由于我想在父组件和子组件之间启用双向数据绑定,因此我还需要定义一个称为此处的参数属性(代表)。去,去,去。命名只是惯例。要点是您必须定义一个属性和与该属性具有相同数据类型的一个。TextChangedValueValueChanged<ChildComponent>TextValueTextChangedValueChangedTextTextChangedValueValueChangedYearYearChangedEventCallback

在父组件内部,我提供如下属性:

<ChildComponent @bind-Text="NameOfAPropertyDefinedInTheParentComponent" /><ChildComponent @bind-Value="NameOfAPropertyDefinedInTheParentComponent" /><ChildComponent @bind-Year="NameOfAPropertyDefinedInTheParentComponent" />

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

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

于 2020-03-12T18:09:09.363 回答