Ven*_*sky 8 c# blazor matblazor asp.net-blazor
我在某些库(MatBlazor、Telerik)中看到了这种具有ValueChanged
和ValueExpression
属性的常见模式,这让我很困惑。
两者有什么区别?什么时候使用它?
Ven*_*sky 12
I would like to add a few use cases for ValueChanged
and 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 e.g. @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.
e.g. from 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; }
Run Code Online (Sandbox Code Playgroud)
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).
Use cases
@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
.
e.g.
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; }
}
Run Code Online (Sandbox Code Playgroud)
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. 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*/);
Run Code Online (Sandbox Code Playgroud)
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.
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. This will make your component have the default value and will also change any property in @bind-Value
to have the default value.
e.g.
// Lifecycle after all parameters are set
protected override void OnParametersSet()
{
// Check if the ValueChanged is set
if (ValueChanged.HasDelegate)
{
ValueChanged.InvokeAsync(DateTime.Now);
}
}
Run Code Online (Sandbox Code Playgroud)
FooExpression
When you have an nullable type, e.g. 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. 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.
Isa*_*aac 11
实际上,您已经忘记了该模式的第三个元素:Value
. 这种“三位一体”的属性经常用于组件双向数据绑定。值得注意的是,这些属性在内置 Blazor 表单组件中使用,例如<InputText>
.
让我们看一个例子:
<InputText @bind-Value="employee.FirstName" />
Run Code Online (Sandbox Code Playgroud)
Value
是以 的形式提供的属性@bind-Value="model.PropertyName"
。
ValueChanged
是 类型EventCallback<TValue>
。它代表更新绑定值的回调。如您所见,我们在上面的示例中没有使用它——它没有必要。编译器知道它的工作并负责它,这意味着它会添加一个EventCallback
“委托”,并在您的背后提供所有必要的设置。
ValueExpression
,最后,指的是标识绑定值的表达式。它由编译器自动创建,您很少(如果有的话)必须设置它。
现在让我们将上面的代码与下面的代码进行比较。以下示例在父组件和子组件之间创建双向数据绑定。但是,我们将不使用标准的“三位一体”(Value
, ValueChanged
, ValueExpression
),而是为自己复制底层模式:
ParentComponent.razor:
<ChildComponent @bind-Text="FirstName" />
@code {
[Parameter]
public string FirstName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
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; }
}
Run Code Online (Sandbox Code Playgroud)
内置的<InputText>
和我们自定义<ChildComponent>
的基本一致!
要回答您的其他问题...
我什么时候会在 Blazor 中使用
ValueChanged
和ValueExpression
?我正在创建来自另一个库的输入的包装器,这是使用三位一体的情况吗?
如上所述,ValueChanged
并且ValueExpression
是在 Blazor 的内置组件中定义的属性,大多数情况下您不需要直接使用它们。
再看看我上面定义的两个组件:<ParentComponent>
和<ChildComponent>
。变化Text
和TextChanged
对Value
和ValueChanged
,和我的部件仍然有效并正常工作。唯一的区别在于命名。我在做什么<ChildComponent>
?我定义了一个名为Text
(代表Value
)的参数属性。由于我想在父组件和子组件之间启用双向数据绑定,因此我还需要定义一个称为此处的参数属性TextChanged
(代表ValueChanged
)。Text
去TextChanged
,Value
去ValueChanged
,Year
去YearChanged
。命名只是约定。要点是您必须定义一个属性和一个EventCallback
与该属性具有相同数据类型的属性。
在父组件中,我提供如下属性:
<ChildComponent @bind-Text="NameOfAPropertyDefinedInTheParentComponent" />
或<ChildComponent @bind-Value="NameOfAPropertyDefinedInTheParentComponent" />
或<ChildComponent @bind-Year="NameOfAPropertyDefinedInTheParentComponent" />
在我上面的组件中,还有一些代码,例如在子组件中,调用TextChanged
委托以便将值传递回父组件;这正是ValueChanged
委托在定义它的组件中所做的。但是您作为用户不必使用它。看看我的组件……它们工作得很好。没必要碰。如果您作为我的组件的用户想要子类化它,那么您需要知道自己在做什么以及如何正确地子类化 Blazor 组件。但是我的组件(部分呈现在这里)相对简单。
假设您想创建一个基于 的密码输入<InputText>
,这不仅可行而且很容易。在这种情况下,除了<InputText>
组件的外观外,您不会更改任何内容,以便显示星号符号而不是普通文本。组件的其余部分不变。您不需要处理事件等。当然,这并不意味着组件作者永远不需要EventCallback
在他的代码中的某处调用。也就是说,我从来没有充分的理由ValueChanged
在使用<InputText>
组件时触发委托。我只需要提供一次ValueExpression
,因为编译器无法识别绑定值。(我会寻找它,如果找到,我会在此处发布...)