Я разрабатываю библиотеку пользовательского интерфейса поверх Blazor, и мне нравится включать синтаксис bind*
для моих компонентов, чтобы потребители тоже могли его использовать.
Я читал и видел множество примеров, поэтому на самом базовом уровне предположим, что у нас есть следующий пользовательский компонент с именем RawCustomInput.razor
:
<input type = "text" value = "@Value" @onchange = "OnValueChanged" style = "width: 5rem" />
@code {
[Parameter]
public string? Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
private async Task OnValueChanged(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
}
И другой компонент, который его потребляет, называется InputPage.razor
вот так:
@page "/input"
<PageTitle>InputPage</PageTitle>
<RawCustomInput
@bind-Value = "@_name" />
@if (!string.IsNullOrEmpty(_name))
{
<p>@_name</p>
}
@code {
private string? _name = null;
}
Теперь вышеприведенное работает, но скажем, я хочу изменить событие с onchange
на oninput
на стороне потребителя? поэтому я попытался сделать что-то подобное @bind-Value:event = "oninput"
, но затем я получаю следующую ошибку:
не имеет свойства, соответствующего имени oninput
Поэтому я подумал, что смогу решить эту проблему, представив @oninput = "OnValueChanged"
в RawCustomInput.razor
, что выглядит так:
<input type = "text" value = "@Value" @onchange = "OnValueChanged" @oninput = "OnValueChanged" style = "width: 5rem" />
@code {
[Parameter]
public string? Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
private async Task OnValueChanged(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
}
Но это не сработало, поэтому я снова прочитал ошибку и понял, что что-то вроде @bind-Value:event = "ValueChanged"
может работать, и это сработало! поэтому я добавил еще одно свойство, которое выглядит так:
[Parameter]
public EventCallback<string> ValueChanging { get; set; }
Итак, теперь мой RawCustomInput.razor
выглядит так:
<input type = "text" value = "@Value" @onchange = "OnChange" @oninput = "OnInput" style = "width: 5rem" />
@code {
[Parameter]
public string? Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[Parameter]
public EventCallback<string> ValueChanging { get; set; }
private async Task OnChange(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
private async Task OnInput(ChangeEventArgs args)
=> await ValueChanging.InvokeAsync(args.Value as string);
}
И, наконец, я мог использовать приведенный ниже синтаксис, который тоже работал, что здорово, но вызывает мало вопросов.
<RawCustomInput
@bind-Value = "@_name"
@bind-Value:event = "ValueChanging" />
bind-{Property} = "{Expression}"
для привязки, но можно ли включить этот синтаксис bind = "{Expression}"
для пользовательских компонентов? потому что, когда я пытаюсь сделать что-то подобное <RawCustomInput @bind = "@_name" />
, выдает следующую ошибку does not have a property matching the name '@bind'
, но это <input type = "text" @bind = "@_name" />
работает, почему?<input type = "text" @bind = "@_name" @bind:event = "oninput" />
но выполнение <RawCustomInput @bind-Value = "@_name" @bind-Value:event = "oninput" />
выдает следующую ошибку does not have a property matching the name 'oninput'
, почему? чтобы добиться аналогичного результата, мне нужно ввести дополнительное свойство, как я указал выше, но тогда кажется немного странным, что для bind:event
мне нужно указать имя события, тогда как для bind-{Property}:event
мне нужно передать свойство, которое может иметь смысл, но я' м не уверен, что понимаю причину.🤔 А знаете ли вы, что...
C# обладает сборщиком мусора, который автоматически управляет памятью, что снижает риск утечек памяти.
Для начала нужно понять разницу между компонентами и элементами.
<input type = "text" value = "@Value" ..
— это объявление элемента. Это html-код.
<RawCustomInput @bind-Value = "@_name" />
— это объявление компонента.
Во-вторых, вы определяете вещи на языке Razor, а не на настоящем C#. @bind
и @bind-value
— это директивы компилятора Razor, которые обрабатываются по-разному.
@bind
используется для связывания элементов. Он компилируется компилятором Razor в код C# следующим образом:
__builder.AddAttribute(19, "value", BindConverter.FormatValue(this.surName));
__builder.AddAttribute(20, "onchange", EventCallback.Factory.CreateBinder(this, __value => this.surName = __value, this.surName));
Обратите внимание, что он использует различные фабричные классы для создания «геттера», чтобы обеспечить правильно отформатированную строку на входе value
, и «сеттера» для обработки сгенерированного события onchange
из входа для обновления переменной.
Для сравнения, это код компонента @bind-value
на компоненте
__builder.AddAttribute(11, "Value", RuntimeHelpers.TypeCheck<String>(this.firstName));
__builder.AddAttribute(12, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<String>>(EventCallback.Factory.Create<String>(this, RuntimeHelpers.CreateInferredEventCallback(this, __value => this.firstName = __value, this.firstName))));
Он устанавливает значение параметра Value
и создает анонимную функцию для установки значения из обратного вызова события ValueChanged
. Нет прямой связи с базовым элементом ввода, объявленным внутри компонента. Он устанавливает параметр компонента и погружает событие компонента. Как они подключены, зависит от вас, дизайнера.
Чтобы продемонстрировать, вот совсем другое подключение компонентов для «флажка»:
<button class = "@this.ButtonCss" @onclick=this.OnValueChanged>@this.ButtonText</button>
@code {
[Parameter] public bool Value { get; set; }
[Parameter] public EventCallback<bool> ValueChanged { get; set; }
private string ButtonCss => Value ? "btn btn-success": "btn btn-danger";
private string ButtonText => Value ? "Enabled" : "Disabled";
private async Task OnValueChanged()
=> await ValueChanged.InvokeAsync(!this.Value);
}
Когда вы объявляете свои компоненты как компоненты Razor, вы ограничены языком Razor. Для 99% компонентов это нормально. Но если вы хотите делать более экзотические вещи, вам нужно вернуться к написанию компонента непосредственно в виде класса C#.
Это показывает один из способов обработки onchange/oninput.
@if (this.ChangeOnInput)
{
<input type = "text" value = "@Value" @oninput = "OnValueChanged" class = "form-control" />
}
else
{
<input type = "text" value = "@Value" @onchange = "OnValueChanged" class = "form-control" />
}
@code {
[Parameter] public string? Value { get; set; }
[Parameter] public bool ChangeOnInput { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
private async Task OnValueChanged(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
}