何时在 Blazor 中使用 ValueChanged 和 ValueExpression?
When to use ValueChanged and ValueExpression in Blazor?
我在某些库(MatBlazor、Telerik)中看到这种具有 ValueChanged
和 ValueExpression
属性的常见模式,这让我很困惑。
两者有什么区别?什么时候使用它?
实际上,您忘记了此模式的第三个元素:Value
。这种“三位一体”属性经常用于组件双向数据绑定。值得注意的是,这些属性在内置的 Blazor 表单组件中使用,例如 <InputText>
.
我们来看一个例子:
<InputText @bind-Value="employee.FirstName" />
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; }
}
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>
基本一样!
回答你的其他问题...
When will I use ValueChanged
and ValueExpression
in Blazor?? I'm creating a wrapper of an input from another library, is this a case for using this trinity?
如上所述,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
。也就是说,我从来没有充分的理由在使用 <InputText>
组件时触发 ValueChanged
委托。而且我只需要提供一次 ValueExpression
,因为编译器无法识别绑定值。 (我会寻找它,如果找到我会 post 这里...)
我想为 ValueChanged
和 ValueExpression
、
添加一些用例
首先,正如 enet 所说,这些属性更像是具有 Foo
、FooChanged
和 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-
包装另一个组件的组件
如果你有一个已经有 @bind-Foo
的组件,你想在它上面创建一个组件并仍然作为参数传递 @bind-Foo
,你只能有一个 属性并传递给@bind-Foo
,需要传递属性给Foo
,FooChanged
and/or 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 - 添加默认值
如果您想在 自定义组件 中使用默认值,您 "can" 只需将默认值传递给 属性。
[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
当你有一个可为 null 的类型时,例如int?
,有时候当值是null
的时候,它并不知道它是什么类型,所以需要传FooExpression
,这样它才能通过反射得到类型。这里有一个example需要用到的地方
如果您正在制作自定义组件并且必须使用绑定 属性 或更改绑定的工作方式,这些属性的用例将被更多地使用。
如果您只使用已经制作好的组件,则很少有需要使用它的情况。
我在某些库(MatBlazor、Telerik)中看到这种具有 ValueChanged
和 ValueExpression
属性的常见模式,这让我很困惑。
两者有什么区别?什么时候使用它?
实际上,您忘记了此模式的第三个元素:Value
。这种“三位一体”属性经常用于组件双向数据绑定。值得注意的是,这些属性在内置的 Blazor 表单组件中使用,例如 <InputText>
.
我们来看一个例子:
<InputText @bind-Value="employee.FirstName" />
的形式提供的属性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; }
}
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>
基本一样!
回答你的其他问题...
When will I use
ValueChanged
andValueExpression
in Blazor?? I'm creating a wrapper of an input from another library, is this a case for using this trinity?
如上所述,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
。也就是说,我从来没有充分的理由在使用 <InputText>
组件时触发 ValueChanged
委托。而且我只需要提供一次 ValueExpression
,因为编译器无法识别绑定值。 (我会寻找它,如果找到我会 post 这里...)
我想为 ValueChanged
和 ValueExpression
、
首先,正如 enet 所说,这些属性更像是具有 Foo
、FooChanged
和 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-
包装另一个组件的组件
如果你有一个已经有 @bind-Foo
的组件,你想在它上面创建一个组件并仍然作为参数传递 @bind-Foo
,你只能有一个 属性并传递给@bind-Foo
,需要传递属性给Foo
,FooChanged
and/or 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 - 添加默认值
如果您想在 自定义组件 中使用默认值,您 "can" 只需将默认值传递给 属性。
[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
当你有一个可为 null 的类型时,例如int?
,有时候当值是null
的时候,它并不知道它是什么类型,所以需要传FooExpression
,这样它才能通过反射得到类型。这里有一个example需要用到的地方
如果您正在制作自定义组件并且必须使用绑定 属性 或更改绑定的工作方式,这些属性的用例将被更多地使用。
如果您只使用已经制作好的组件,则很少有需要使用它的情况。