Когда использовать ValueChanged и ValueExpression в Blazor?

Я вижу этот общий шаблон в некоторых библиотеках (MatBlazor, Telerik) наличия свойств ValueChanged и ValueExpression, и это меня действительно смущает.

В чем разница между ними? А когда его использовать?


person Vencovsky    schedule 12.03.2020    source источник
comment
ValueChanged - это событие двунаправленной привязки данных, ValueExpression - это выражение Linq для привязанного свойства.   -  person agua from mars    schedule 12.03.2020
comment
Почему вы спрашиваете ?   -  person enet    schedule 12.03.2020
comment
@enet, потому что я хочу знать, как это работает и для чего?   -  person Vencovsky    schedule 12.03.2020
comment
Хорошо, нет проблем...   -  person enet    schedule 12.03.2020


Ответы (2)


Я хотел бы добавить несколько вариантов использования для 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 и / или 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; }        
}

Такая ситуация обертывания другого компонента будет происходить часто, если вы создаете много настраиваемых компонентов или не хотите напрямую использовать какой-либо сторонний компонент.

Пример моего проекта: в моем проекте я использую MatBlazor и Telerik, но не все компоненты в обеих библиотеках полностью стабильны, поэтому я создал оболочку для всех компонентов и однажды, когда одна из этих библиотек полностью станет стабильный, я перейду на использование только одной библиотеки. Это позволяет мне иметь свои пользовательские компоненты, и если я хочу изменить один, я изменяю только одну вещь в моем пользовательском компоненте и изменяю все приложение.

2 - Добавление значения по умолчанию

Если вы хотите иметь значение по умолчанию внутри настраиваемого компонента, вы «можете» просто передать значение по умолчанию в свойство.

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

Но это большая проблема, если вы используете этот компонент внутри формы.

Почему? Потому что вы измените значение только внутри своего компонента, но если свойство передается в @bind-Value, оно не изменится.

Чтобы добавить это значение по умолчанию и заставить его работать при двусторонней привязке данных, вам нужно вызвать ValueChanged и передать значение по умолчанию. Это приведет к тому, что ваш компонент будет иметь значение по умолчанию, а также изменит любое свойство в @bind-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);
    }
}

3 - Вариант использования, когда вам действительно нужно FooExpression

Если у вас есть тип, допускающий значение NULL, например int?, иногда, когда значение равно null, он не может знать его тип, поэтому вам нужно передать FooExpression, чтобы он мог получить тип путем отражения. Вот пример, в котором вам нужно его использовать.


Вариант использования этих свойств будет использоваться чаще, если вы создаете настраиваемые компоненты и должны работать с привязанным свойством или изменять способ работы привязки.

Если вы используете только уже изготовленные компоненты, то в редких случаях вам придется их использовать.

person Vencovsky    schedule 01.04.2020

Фактически, вы забыли третий элемент этого шаблона: 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> в основном одинаковы!


Чтобы ответить на ваш другой вопрос ...

Когда я буду использовать ValueChanged и ValueExpression в Blazor ?? Я создаю оболочку ввода из другой библиотеки, это случай использования этой троицы?

Как объяснялось выше, 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" /> or <ChildComponent @bind-Value="NameOfAPropertyDefinedInTheParentComponent" /> or <ChildComponent @bind-Year="NameOfAPropertyDefinedInTheParentComponent" />

В моих компонентах выше также есть код, как, например, в дочернем компоненте, который вызывает делегат TextChanged, чтобы передать значение обратно родительскому компоненту; именно это делает ValueChanged делегат в компонентах, в которых он определен. Но вам как пользователю не обязательно его использовать. Посмотрите на мои компоненты ... Они отлично работают. Не нужно трогать. Если вы, как пользователь моего компонента, хотите создать его подкласс, вам необходимо знать, что вы делаете и как правильно создать подкласс компонента Blazor. Но мои компоненты, частично представленные здесь, относительно просты.

Предположим, вы хотите создать ввод пароля на основе <InputText>, что не только выполнимо, но и довольно просто. В этом случае вы не собираетесь ничего менять, кроме внешнего вида компонента <InputText>, чтобы вместо обычного текста отображались символы звездочки. В остальном компонент без изменений. Вам не нужно обрабатывать события и тому подобное. Это, конечно, не означает, что автору компонента никогда не нужно будет вызывать EventCallback откуда-то в своем коде. Тем не менее, у меня никогда не было веской причины запускать делегат ValueChanged при использовании компонента <InputText>. И мне только один раз пришлось указать ValueExpression, так как компилятор не смог определить связанное значение. (Поищу, а если найду, выложу сюда ...)

person enet    schedule 12.03.2020
comment
Когда я буду использовать ValueChanged и ValueExpression в Blazor ?? Я создаю оболочку ввода из другой библиотеки, это случай использования этой троицы? - person Vencovsky; 12.03.2020
comment
Отличный ответ, это косвенно объясняет, что это соглашение Blazor, а MatBlazor и Telerik следуют соглашению, установленному фреймворком. - person Ed Charbeneau; 12.03.2020
comment
@Ed Charbeneau, спасибо за комплименты ... Я польщен, так как я многому научился из вашей работы ... - person enet; 12.03.2020
comment
Кстати, говоря о Telerik, я теперь помню, что это было единственное место, где я видел образец, в котором разбирались и объяснялись ValueExpression. Чем ты. - person enet; 12.03.2020
comment
Это потрясающе. Рад слышать, что вы нашли мою работу полезной. - person Ed Charbeneau; 13.03.2020
comment
@Vencovsky, насколько я понимаю, всякий раз, когда вы реализуете Компоненты, использующие двустороннюю привязку, вы должны использовать Value и ValueChanged. Если вы используете функциональность, которая зависит от идентификации связанного свойства (например, реализация проверки). Тогда вам также нужно использовать ValueExpression. Например, вы можете создать FieldIdentifier, используя FieldIdentifier.Create(ValueExpression). - Надеюсь это поможет. - person LuckyLikey; 16.02.2021