#asp.net-core #blazor #blazor-server-side #asp.net-blazor
#asp.net-ядро #блейзор #блейзор — на стороне сервера #asp.net-blazor
Вопрос:
У меня есть требование динамически помещать компонент Блейзора в пользовательский контент. По сути, предполагается, что компонент расширяет пользовательскую разметку некоторыми элементами пользовательского интерфейса.
Допустим, пользователь предоставляет некоторый контент, в котором может быть элемент «приветствие-контейнер», и компонент должен вставить кнопку приветствия внутри этого элемента.
Мое текущее решение — вызвать функцию JavaScript для перемещения элемента DOM OnAfterRenderAsync
(полный код ниже). Кажется, он работает нормально, но манипулирование элементами DOM, похоже, не рекомендуется в Blazor, поскольку это может повлиять на алгоритм diffing. Итак, у меня есть пара вопросов по этому поводу:
- Насколько плохо перемещать элементы DOM таким образом? Вызывает ли это проблемы с производительностью, функциональные проблемы или какое-либо неопределенное поведение?
- Есть ли лучший способ добиться того же результата без использования JavaScript? Я рассматривал возможность использования
RenderTreeBuilder
для этого, но, похоже, он может быть не предназначен для этой цели, поскольку рекомендуется использовать жестко заданные порядковые номера, что представляется невозможным при работе с динамическим контентом, неизвестным во время компиляции.
Текущий код решения:
Приветствие.razor
@page "/greeter"
@inject IJSRuntime JSRuntime;
<div>
@((MarkupString)UserContentMarkup)
<div id="greeting">
<button @onclick="ToggleGreeting">Toggle greeting</button>
@if (isGreetingVisible) {
<p>Hello, @Name!</p>
}
</div>
</div>
@code {
[Parameter]
public string UserContentMarkup { get; set; }
[Parameter]
public string Name { get; set; }
private bool isGreetingVisible;
private void ToggleGreeting()
{
isGreetingVisible = !isGreetingVisible;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JSRuntime.InvokeVoidAsync("moveGreetingToContainer");
}
}
_Host.cshtml
window.moveGreetingToContainer = () => {
var greeting = document.getElementById("greeting");
var container = document.getElementById("greeting-container");
container.appendChild(greeting);
}
UserContentTest.razor
@page "/userContentTest"
@inject IJSRuntime JSRuntime;
<h3>Testing user content</h3>
<Greeter UserContentMarkup=@userContentMarkup Name="John"></Greeter>
@code {
private string userContentMarkup = "Some <b>HTML</b> text followed by greeting <div id='greeting-container'></div> and <i>more</i> text";
}
Ожидаемый результат (после нажатия кнопки «Переключить приветствие»):
<div>
Some <b>HTML</b> text followed by greeting
<div id="greeting-container">
<div id="greeting">
<button>Toggle greeting</button>
<p>Hello, John!</p>
</div>
</div> and <i>more</i> text
</div>
Ответ №1:
Отличный вопрос — и да, использование JS для перемещения элементов dom очень плохо, поскольку Blazor не видит изменений, внесенных вами в dom.
Что вы можете сделать, так это переключиться на использование RenderFragment
и, более конкретно RenderFragment<RenderFragment>
, на разметку, которая будет снабжена дополнительной разметкой в качестве параметра.
Во второй строке я вызываю метод UserContentMarkup (который является a RenderFragment<RenderFragment>
) и передаю <div id=greeting>
содержимое в качестве context
параметра.
Примечание: он заключен в <text>
элемент, который на самом деле является способом встраивания HTML в C # в файл Razor. Он не отображает <text>
элемент на странице.
Приветствие.razor
<div>
@UserContentMarkup(
@<text>
<div id="greeting">
<button @onclick="ToggleGreeting">Toggle greeting</button>
@if (isGreetingVisible) {
<p>Hello, @Name!</p>
}
</div>
</text>
)
</div>
@code {
[Parameter]
public RenderFragment<RenderFragment> UserContentMarkup { get; set; }
[Parameter]
public string Name { get; set; }
private bool isGreetingVisible;
private void ToggleGreeting()
{
isGreetingVisible = !isGreetingVisible;
}
}
UserContentTest.razor
Здесь вы можете увидеть два способа использования Greeter — с помощью разметки на странице или с помощью code
метода.
<h3>Testing user content</h3>
@* Using markup to supply user content - @context is where the button goes *@
<Greeter Name="John">
<UserContentMarkup>
Some <b>HTML</b> text followed by greeting
<div id='greeting-container'>@context</div> and <i>more</i> text
</UserContentMarkup>
</Greeter>
@* Using a method to supply the user content - @context is where the button goes *@
<Greeter Name="John" UserContentMarkup=@userContent />
Этот code
метод может сбивать с толку — это a RenderFragment<RenderFragment>
, что означает, что это должен быть метод, который принимает a RenderFragment
в качестве своего единственного параметра и возвращает a RenderFragment
— RenderFragment
в данном случае возвращаемая разметка завернута, <text>
чтобы было ясно, что это разметка.
@code
{
RenderFragment<RenderFragment> userContent
=> context => @<text>Some stuff @context more stuff</text>;
}
Попробуйте это здесь: https://blazorrepl.com/repl/QuPPaMEu34yA5KSl40
Комментарии:
1. Спасибо! Это выглядит почти точно так, как мне нужно. Единственный дополнительный вопрос заключается в том, может ли эта строка «<text>Some stuff @context more stuff</text>» предоставляться динамически, как загружается из базы данных? Поскольку это пользовательский контент, который мы не знаем заранее
2. Да, но вам понадобится заполнитель для контекста и замените его во время выполнения
3. Есть ли какой-либо пример этого? Из документов, которые я смог найти, казалось, что вся разметка должна быть предоставлена во время компиляции, чтобы «Компилятор Blazor» мог перевести ее в html. Но если есть какой-либо способ получить его динамически, это будет действительно здорово
4. Я полагаю, вы имеете в виду наличие заполнителя для контекста и замену его с помощью JavaScript после отображения компонента?
5. Нет без JS. Но, возможно, было бы проще просто иметь два фрагмента рендеринга для pre и post