Расположите компонент Блейзора внутри пользовательского содержимого

#asp.net-core #blazor #blazor-server-side #asp.net-blazor

#asp.net-ядро #блейзор #блейзор — на стороне сервера #asp.net-blazor

Вопрос:

У меня есть требование динамически помещать компонент Блейзора в пользовательский контент. По сути, предполагается, что компонент расширяет пользовательскую разметку некоторыми элементами пользовательского интерфейса.

Допустим, пользователь предоставляет некоторый контент, в котором может быть элемент «приветствие-контейнер», и компонент должен вставить кнопку приветствия внутри этого элемента.

Мое текущее решение — вызвать функцию JavaScript для перемещения элемента DOM OnAfterRenderAsync (полный код ниже). Кажется, он работает нормально, но манипулирование элементами DOM, похоже, не рекомендуется в Blazor, поскольку это может повлиять на алгоритм diffing. Итак, у меня есть пара вопросов по этому поводу:

  1. Насколько плохо перемещать элементы DOM таким образом? Вызывает ли это проблемы с производительностью, функциональные проблемы или какое-либо неопределенное поведение?
  2. Есть ли лучший способ добиться того же результата без использования 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