Содержание
Динамические компоненты Razor удобно использовать в том случае, когда мы заранее не знаем как будет строится пользовательский интерфейс. Например, наше приложение может использовать систему виджетов и пользователь сам определяет какие виджеты необходимо вывести на главной странице. В этом случае нам потребуется обеспечить динамическое создание компонентов.
Компонент <DynamicComponent>
<DynamicComponent>
— это встроенный компонент Razor, использование которого в разметке выглядит следующим образом:
<DynamicComponent Type="componentType" Parameters="parameters" /> @code { private Type componentType = ...; private IDictionary<string, object> parameters = ...; }
Параметр Type
определяет тип компонента, который будет показан пользователю, а Parameters
— список параметров, которые будут переданы в компонент. Посмотрим на работу этого компонента на примере.
Пример использования DynamicComponent
В качестве примера, будем размещать на главной странице приложения (компонент Home.razor
) различные дочерние компоненты, которые будет выбирать пользователь. Итак, создадим новое приложение Blazor Hybrid и добавим в папку Components следующие компоненты:
1. Компонент Clock.razor (часы)
<h4>Часы</h4> @if (timer != null) { <p>Дата: @time.ToString("D")</p> <p>Время: @time.ToString("T")</p> } @code { System.Threading.Timer? timer; DateTime time = DateTime.Now; private async void SetClock(object stateInfo) { time = DateTime.Now; await InvokeAsync(StateHasChanged); } protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); timer = new System.Threading.Timer(SetClock, new System.Threading.AutoResetEvent(false), 1000, 1000); } }
2. Компонент Message.razor (сообщение)
<div class="alert alert-danger" role="alert"> @Text </div> @code{ [Parameter] public string Text { get; set; } = "Важное сообщение"; }
Третьим компонентов будет выступать уже имеющийся в приложении счётчик (Counter.razor) с небольшими изменениями.
3. Шаблонный компонент Counter.razor (счётчик)
@page "/counter" <h4>Counter</h4> <p role="status">Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; [Parameter] public EventCallback OnMaxValue { get; set; } private async Task IncrementCount() { if (currentCount == 10) await OnMaxValue.InvokeAsync(); else currentCount++; } }
Вот эти три компонента мы и будем динамически отображать на главной странице приложения. Теперь перейдем в файл Home.razor и изменим его код следующим образом:
@page "/" <div class="d-flex justify-content-between"> <div> <input class="form-check-input" type="checkbox" id="clock" name="clock" @bind-value="showClock" /> <label class="form-check-label" for="clock">Часы</label> </div> <div> <input class="form-check-input" type="checkbox" id="message" name="message" @bind-value="showMessage" /> <label class="form-check-label" for="message">Сообщение</label> </div> <div> <input class="form-check-input" type="checkbox" id="counter" name="counter" @bind-value="showCounter" /> <label class="form-check-label" for="counter">Счётчик</label> </div> </div> @if (showClock) { <DynamicComponent Type="typeof(Clock)"></DynamicComponent> } @if (showMessage) { <DynamicComponent Type="typeof(Message)"></DynamicComponent> } @if (showCounter) { <DynamicComponent Type="typeof(Counter)"></DynamicComponent> } @code{ bool showCounter = false; bool showClock = false; bool showMessage = false; }
Рассмотрим подробнее этот код. В разметке мы определили три html-элемента типа checkbox
которым мы сделали привязку к их свойству value
, используя три переменные в коде компонента:
bool showCounter = false; bool showClock = false; bool showMessage = false;
Например, для первого чекбокса код разметки выглядит так:
<div> <input class="form-check-input" type="checkbox" id="clock" name="clock" @bind-value="showClock" /> <label class="form-check-label" for="clock">Часы</label> </div>
В зависимости от состояния чекбокса мы либо показываем компонент на главной странице приложения, либо прячем:
@if (showClock) { <DynamicComponent Type="typeof(Clock)"></DynamicComponent> } @if (showMessage) { <DynamicComponent Type="typeof(Message)"></DynamicComponent> } @if (showCounter) { <DynamicComponent Type="typeof(Counter)"></DynamicComponent> }
Запустим приложение и посмотрим на результат:
Соответственно, когда вы отмечаете чекбокс — компонент появляется, убираете — компонент скрывается. Так в самом простом случае работает встроенный компонент <DynamicComponent>
. Как вы могли заметить, наш компонент «Сообщение» выводит сообщение по умолчанию, хотя у этого компонента определен параметр Text
, с помощью которого мы можем задавать собственный текст сообщения. Рассмотрим как мы это можем делать с использованием <DynamicComponent>
.
Передача параметров в динамические компоненты Razor
Для передачи параметров в динамические компоненты Razor используется такой же механизм, как и при передаче произвольного набора параметров в html-элементы. То есть, мы должны сформировать словарь вида Dictionary<string, object>
, где ключом является имя параметра динамического компонента и передать этот словарь в параметре Parameters
<DynamicComponent>
. Попробуем передать в наш компонент «Сообщение» произвольный текст — перепишем код Home.razor следующим образом:
@page "/" <div class="d-flex justify-content-between"> <div> <input class="form-check-input" type="checkbox" id="clock" name="clock" @bind-value="showClock" /> <label class="form-check-label" for="clock">Часы</label> </div> <div> <input class="form-check-input" type="checkbox" id="message" name="message" @bind-value="showMessage" /> <label class="form-check-label" for="message">Сообщение</label> </div> <div> <input class="form-check-input" type="checkbox" id="counter" name="counter" @bind-value="showCounter" /> <label class="form-check-label" for="counter">Счётчик</label> </div> </div> @if (showClock) { <DynamicComponent Type="typeof(Clock)"></DynamicComponent> } @if (showMessage) { <DynamicComponent Type="typeof(Message)" Parameters="messageParameters"></DynamicComponent> } @if (showCounter) { <DynamicComponent Type="typeof(Counter)"></DynamicComponent> } @code{ bool showCounter = false; bool showClock = false; bool showMessage = false; Dictionary<string, object> messageParameters = new() { { "Text","Это текст для сообщения, переданный из Home.razor"} }; }
В коде компонента мы сформировали словарь с параметрами для компонента:
Dictionary<string, object> messageParameters = new() { { "Text","Это текст для сообщения, переданный из Home.razor"} };
А в разметке передали этот словарь компоненту:
@if (showMessage) { <DynamicComponent Type="typeof(Message)" Parameters="messageParameters"></DynamicComponent> }
Результат будет следующим:
Здесь мы передали в параметрах обычную строку, однако, при необходимости, мы можем передавать в динамические компоненты различные объекты и методы обратного вызова для обработки событий компонента.
Обработка событий в динамических компонентах
Чтобы продемонстрировать передачу методов обратного вызова в динамические компоненты, воспользуемся компонентом Counter у которого определено следующее событие:
[Parameter] public EventCallback OnMaxValue { get; set; }
Изменим код Home.razor следующим образом:
@page "/" <div class="d-flex justify-content-between"> <div> <input class="form-check-input" type="checkbox" id="clock" name="clock" @bind-value="showClock" /> <label class="form-check-label" for="clock">Часы</label> </div> <div> <input class="form-check-input" type="checkbox" id="message" name="message" @bind:get="showMessage" @bind:set="(value)=>{showMessage=value;}" /> <label class="form-check-label" for="message">Сообщение</label> </div> <div> <input class="form-check-input" type="checkbox" id="counter" name="counter" @bind-value="showCounter" /> <label class="form-check-label" for="counter">Счётчик</label> </div> </div> @if (showClock) { <DynamicComponent Type="typeof(Clock)"></DynamicComponent> } @if (showMessage) { <DynamicComponent Type="typeof(Message)" Parameters="messageParameters"></DynamicComponent> } @if (showCounter) { <DynamicComponent Type="typeof(Counter)" Parameters="counterParameters"></DynamicComponent> } @code{ bool showCounter = false; bool showClock = false; bool showMessage = false; private Dictionary<string, object> counterParameters = new(); public Dictionary<string, object> messageParameters = new() { { "Text","Это текст для сообщения, переданный из Home.razor"} }; private void OnCounterMaxValue() { messageParameters.Clear(); messageParameters.Add("Text", "Счётчик достиг максимального значения"); showMessage = true; } protected override void OnInitialized() { counterParameters[nameof(Counter.OnMaxValue)] = EventCallback.Factory.Create(this, OnCounterMaxValue); } }
Первое, что мы изменили — это установили двухстороннюю привязку для чекбокса, отвечающего за вывод сообщения:
<div> <input class="form-check-input" type="checkbox" id="message" name="message" @bind:get="showMessage" @bind:set="(value)=>{showMessage=value;}" /> <label class="form-check-label" for="message">Сообщение</label> </div>
Зачем мы это сделали — показано далее. Второй момент — это мы добавили новый словарь
private Dictionary<string, object> counterParameters = new();
В котором мы будем передавать параметры для компонента Counter
.
Третий момент — мы переопределили метод OnInitialized()
где создали объект словаря и записали в него передаваемый параметр — метод обратного вызова:
protected override void OnInitialized() { counterParameters[nameof(Counter.OnMaxValue)] = EventCallback.Factory.Create(this, OnCounterMaxValue); }
Здесь стоит обратить внимание на то как передается обработчик события — мы воспользовались методом Create
свойства Factory
у EventCallback
. Свойство Factory
представлено объектом типа Event
— фабрика для создания экземпляров EventCallback
и EventCallback<T>
.
В метод Event
мы передаем два параметра:
receiver
— приемник сообщений — любой объект, обрабатывающий событиеcallback
— действиеAction<Object>
— собственно, сам метод обратного вызова
Метод обратного вызова у нас выглядит следующим образом:
private void OnCounterMaxValue() { messageParameters.Clear(); messageParameters.Add("Text", "Счётчик достиг максимального значения"); showMessage = true; }
Здесь мы назначаем компоненту Message
новое значение параметра Text
и программно включаем чекбокс для показа сообщения, изменяя значение у showMessage
. Именно по этому мы изменили простую привязку к свойству value
чекбокса на двухстороннюю. Если бы мы этого не сделали, то программное изменение значения showMessage
никак бы не влияло на состояние чекбокса.
Теперь запустим приложение и включим счётчик. Вид приложения будет следующим:
Как только счётчик достигнет значения 10, на одиннадцатом клике вид приложения изменится следующим образом:
Таким образом, можно сказать, что используя параметры динамического компонента, мы успешно передали в него метод обратного вызова для обработки событий. При этом, вы можете сразу показать сообщение послед запуска приложения, а потом прокликать счётчик и на одиннадцатом клике сообщение автоматически поменяется.
Итого
Динамические компоненты Razor в разметке представлены компонентами <DynamicComponent>
и позволяют отображать обычные компоненты Razor в зависимости от различных факторов, которые трудно или невозможно предугадать заранее. В динамические компоненты можно передавать как обычные параметры, представленные простыми типами или целыми объектами, так и методы обратного вызова для обработки событий в дочерних компонентах.