[英]When to use ValueChanged and ValueExpression in Blazor?
I'm seeing this common pattern in some libraries (MatBlazor, Telerik) of having ValueChanged
and ValueExpression
properties and it really confuses me.我在某些库(MatBlazor、Telerik)中看到这种具有ValueChanged
和ValueExpression
属性的常见模式,这让我很困惑。
What is the difference between both?两者有什么区别? And when to use it?什么时候使用它?
I would like to add a few use cases for ValueChanged
and ValueExpression
,我想为ValueChanged
和ValueExpression
添加一些用例,
First of all, as enet said, these properties are more like a trinity of properties where you have Foo
, FooChanged
and FooExpression
and it's used in the two-way data bind eg @bind-Foo="SomeProperty"
.首先,正如 enet 所说,这些属性更像是一个三位一体的属性,其中有Foo
、 FooChanged
和FooExpression
,它用于双向数据绑定,例如@bind-Foo="SomeProperty"
。
To create a custom component with a property that can be used with @bind-
you need to provide these 3 properties (only providing Foo
and FooChanged
also work) as [Parameter]
and call FooChanged
when the property inside your custom component changes.要创建具有可与使用的属性自定义组件@bind-
您需要提供这些3个属性(仅提供Foo
和FooChanged
也工作)的[Parameter]
和呼叫FooChanged
当你的自定义组件的改变里面的属性。
eg from enet例如来自 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; }
Adding the @bind-Foo
would be the same as passing Value
and ValueChanged
, the only difference is that @bind-
will only set the property, but if you add your own ValueChanged
, you can do anything you want (Validating, Changing the value to set, etc).添加@bind-Foo
将与传递Value
和ValueChanged
相同,唯一的区别是@bind-
只会设置属性,但是如果您添加自己的ValueChanged
,您可以做任何您想做的事情(验证,更改值设置等)。
Use cases用例
@bind-
1 - 创建一个组件,用@bind-
包装另一个组件If you have an component that already have a @bind-Foo
and you want to create a component on top of that and still pass as parameter @bind-Foo
, you can have only one property and pass to @bind-Foo
, you need to pass properties to Foo
, FooChanged
and/or FooExpression
.如果你有一个已经有@bind-Foo
的组件,并且你想在它之上创建一个组件并且仍然作为参数@bind-Foo
传递,你只能有一个属性并传递给@bind-Foo
,你需要将属性传递给Foo
、 FooChanged
和/或FooExpression
。
eg例如
CustomInputWrapper.razor 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; }
}
These situation of wrapping another component will happen a lot if you are making a lot of custom components or don't want to use directly some third party component.如果您正在制作大量自定义组件或不想直接使用某些第三方组件,那么这种包装另一个组件的情况会发生很多。
Example of my project: In my project I'm using MatBlazor and Telerik, but not all of the components in both libraries are completely stable, so I created a wrapper around all of the components and one day, when one of these libraries is completely stable, I will change to use only one library.我的项目示例:在我的项目中,我使用 MatBlazor 和 Telerik,但并非两个库中的所有组件都完全稳定,因此我围绕所有组件创建了一个包装器,有一天,当这些库中的一个完全稳定时稳定,我会改为只使用一个库。 Doing this allow me to have my custom components and if I want to change one, I only change one thing In my custom component and changes the whole application.这样做允许我拥有自定义组件,如果我想更改一个组件,我只更改自定义组件中的一件事并更改整个应用程序。
If you want to have a default value inside a custom component , you "can" just pass a default value to the property.如果您想在自定义组件中有一个默认值,您“可以”只将一个默认值传递给该属性。
[Parameter]
public virtual DateTime Value { get; set; } = new DateTime(/* some default value*/);
But this have a big problem if you use this component inside a form.但是如果你在表单中使用这个组件,这会有一个大问题。
Why ?为什么? Because you will only change the value inside your component, but if a property is passed in @bind-Value
it won't be changed.因为您只会更改组件内部的值,但如果在@bind-Value
传递属性,则不会更改。
To add this default value and make it work in the two-way data bind, you need to call ValueChanged
and pass the default value.要添加此默认值并使其在双向数据绑定中工作,您需要调用ValueChanged
并传递默认值。 This will make your component have the default value and will also change any property in @bind-Value
to have the default value.这将使您的组件具有默认值,并且还将更改@bind-Value
任何属性以具有默认值。
eg例如
// Lifecycle after all parameters are set
protected override void OnParametersSet()
{
// Check if the ValueChanged is set
if (ValueChanged.HasDelegate)
{
ValueChanged.InvokeAsync(DateTime.Now);
}
}
FooExpression
3 - 真正需要FooExpression
用例When you have an nullable type, eg int?
当您有一个可为空类型时,例如int?
, sometimes, when the value is null
, it can't know it's type, so you need to pass FooExpression
so it can get the type by reflection. ,有时,当值为null
,它无法知道它的类型,因此您需要传递FooExpression
以便它可以通过反射获取类型。 Here is an example where you need to use it.这是您需要使用它的示例。
The use case of these properties will be used more if you are making custom components and have to work with binded property or change on how the bind will work.如果您正在制作自定义组件并且必须使用绑定属性或更改绑定的工作方式,则这些属性的用例将被更多地使用。
If you are only using already made components, it will be rare the cases where you will have to use it.如果您只使用已经制作好的组件,那么您很少会需要使用它。
Actually, you've forgotten the third element of this pattern: Value
.实际上,您已经忘记了该模式的第三个元素: Value
。 This "trinity" of properties is frequently used for component two-way data binding.这种“三位一体”的属性经常用于组件双向数据绑定。 Notably, these properties are employed inside the built-in Blazor form components, such as <InputText>
.值得注意的是,这些属性在内置 Blazor 表单组件中使用,例如<InputText>
。
Let's look at an example:让我们看一个例子:
<InputText @bind-Value="employee.FirstName" />
Value
is a property provided in the form of @bind-Value="model.PropertyName"
. Value
是一个以@bind-Value="model.PropertyName"
形式提供的属性。
ValueChanged
is of type EventCallback<TValue>
. ValueChanged
是EventCallback<TValue>
类型。 It stands for a callback that updates the bound value.它代表更新绑定值的回调。 As you can see, we do not use it in the above example—it's not necessary.如您所见,我们在上面的示例中没有使用它——它没有必要。 The compiler knows its job and it takes care of this, meaning that it adds an EventCallback
"delegate" with all the necessary settings behind your back.编译器知道它的工作并处理它,这意味着它会添加一个EventCallback
“委托”,并在您的背后提供所有必要的设置。
ValueExpression
, finally, refers to an expression that identifies the bound value.最后, ValueExpression
是指标识绑定值的表达式。 It is automatically created by the compiler, and you rarely, if ever, have to set it.它由编译器自动创建,您很少(如果有的话)必须设置它。
Now let's compare the above with the code below.现在让我们将上面的代码与下面的代码进行比较。 The following example creates a two-way data binding between a parent component and a child component.以下示例在父组件和子组件之间创建双向数据绑定。 However, instead of using the standard "trinity" ( Value
, ValueChanged
, ValueExpression
), we will replicate the underlying pattern for ourselves:然而,我们不使用标准的“三位一体”( Value
、 ValueChanged
、 ValueExpression
),而是为自己复制底层模式:
ParentComponent.razor: ParentComponent.razor:
<ChildComponent @bind-Text="FirstName" />
@code {
[Parameter]
public string FirstName { get; set; }
}
ChildComponent.razor: 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; }
}
The built-in <InputText>
and our custom <ChildComponent>
are basically the same!内置的<InputText>
和我们自定义的<ChildComponent>
基本一致!
To answer your other question...要回答您的其他问题...
When will I use
ValueChanged
andValueExpression
in Blazor??我什么时候可以使用ValueChanged
和ValueExpression
在Blazor? I'm creating a wrapper of an input from another library, is this a case for using this trinity?我正在创建来自另一个库的输入的包装器,这是使用三位一体的情况吗?
As explained above, ValueChanged
and ValueExpression
are properties defined in Blazor's built-in components, and most of the time you won't need to use them directly.如上所述, ValueChanged
和ValueExpression
是在 Blazor 的内置组件中定义的属性,大多数情况下不需要直接使用它们。
Look again at the two components I've defined above: <ParentComponent>
and <ChildComponent>
.再看看我上面定义的两个组件: <ParentComponent>
和<ChildComponent>
。 Change Text
and TextChanged
to Value
and ValueChanged
, and my components are still valid and work correctly.将Text
和TextChanged
更改为Value
和ValueChanged
,我的组件仍然有效并正常工作。 The only difference is in naming.唯一的区别在于命名。 What do I do in the <ChildComponent>
?我在<ChildComponent>
做什么? I define a parameter property named Text
(stands for Value
).我定义了一个名为Text
(代表Value
)的参数属性。 As I want to enable two-way data binding between the parent and child components, I also need to define a parameter property called here TextChanged
(stands for ValueChanged
).因为我想在父组件和子组件之间启用双向数据绑定,所以我还需要定义一个名为TextChanged
的参数属性(代表ValueChanged
)。 Text
goes to TextChanged
, Value
goes to ValueChanged
, and Year
goes to YearChanged
. Text
转到TextChanged
, Value
转到ValueChanged
,而Year
转到YearChanged
。 The naming is only convention.命名只是约定。 The main point is that you have to define a property and an EventCallback
of the same data type as the property.要点是您必须定义一个属性和一个与该属性具有相同数据类型的EventCallback
。
Inside the parent component I provide the property as follows:在父组件中,我提供如下属性:
<ChildComponent @bind-Text="NameOfAPropertyDefinedInTheParentComponent" />
or <ChildComponent @bind-Value="NameOfAPropertyDefinedInTheParentComponent" />
or <ChildComponent @bind-Year="NameOfAPropertyDefinedInTheParentComponent" />
<ChildComponent @bind-Text="NameOfAPropertyDefinedInTheParentComponent" />
或<ChildComponent @bind-Value="NameOfAPropertyDefinedInTheParentComponent" />
或<ChildComponent @bind-Year="NameOfAPropertyDefinedInTheParentComponent" />
In my components above, there is also code, as for instance in the child component, that invokes the TextChanged
delegate in order to pass a value back to the parent component;在我上面的组件中,还有一些代码,例如在子组件中,它调用TextChanged
委托以便将值传递回父组件; this is exactly what the ValueChanged
delegate does in the components in which it is defined.这正是ValueChanged
委托在定义它的组件中所做的。 But you as a user do not have to use it.但是您作为用户不必使用它。 Look at my components... They work perfectly well.看看我的组件……它们工作得很好。 No need to touch.没必要碰。 If you as a user of my component want to subclass it, then you need to know what you're doing and how to subclass a Blazor component properly.如果您作为我的组件的用户想要子类化它,那么您需要知道自己在做什么以及如何正确地子类化 Blazor 组件。 But my components, partially presented here, are relatively simple.但是我的组件(部分呈现在这里)相对简单。
Suppose you want to create a password input based on <InputText>
, which is not only doable but quite easy.假设您想基于<InputText>
创建一个密码输入,这不仅可行而且很容易。 In that case, you're not going to change anything but the look of the <InputText>
component so that asterisk symbols are displayed instead of normal text.在这种情况下,除了<InputText>
组件的外观外,您不会更改任何内容,以便显示星号符号而不是普通文本。 The rest of the component is unchanged.组件的其余部分不变。 You do not need to handle the events and such.您不需要处理事件等。 This, of course, does not mean that a component author will never need to call the EventCallback
from somewhere in his code.当然,这并不意味着组件作者永远不需要从他的代码中的某个地方调用EventCallback
。 That said, I have never had a good reason to trigger the ValueChanged
delegate when using the <InputText>
component.也就是说,在使用<InputText>
组件时,我从来没有充分的理由触发ValueChanged
委托。 And I only once had to provide a ValueExpression
, as the compiler was not able to identify the bound value.我只需要提供一次ValueExpression
,因为编译器无法识别绑定值。 (I'll look for it, and if found I'll post it here...) (我会寻找它,如果找到,我会在此处发布...)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.