在用户内容中放置Blazor组件 Greeter.razor UserContentTest.razor

问题描述

我需要动态地将Blazor组件放入用户提供的内容中。本质上,该组件应该使用一些UI元素扩展用户提供的标记

比方说,用户提供了一些可以在其中包含“ greeting-container”元素的内容,并且该组件应在该元素内插入一个问候按钮。

我当前的解决方案是调用JavaScript函数来移动OnAfterRenderAsync中的DOM元素(下面的完整代码)。它似乎工作正常,但是在Blazor中似乎不建议使用DOM元素,因为它会影响差异算法。所以我对此有两个问题:

  1. 像这样移动DOM元素有多糟?它会导致性能问题,功能问题或某些未定义的行为吗?
  2. 是否有更好的方法可以在不使用JavaScript的情况下达到相同的结果?我当时正在考虑使用RenderTreeBuilder,但似乎并非为此目的而设计,因为建议使用硬编码的序列号,这在处理编译时未知的动态内容时似乎不可能

当前解决方代码

Greeter.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>

解决方法

一个好问题-是的,由于Blazor看不到您对dom所做的更改,因此使用JS移动dom元素非常糟糕。

您可以做的是切换到使用RenderFragment,更具体地说是RenderFragment<RenderFragment>,它是将提供更多标记作为参数的标记。

在第二行中,我正在调用UserContentMarkup方法(它是RenderFragment<RenderFragment>),并将<div id=greeting>内容作为context参数传递。

注意:它包装在<text>元素中,这实际上是将HTML嵌入Razor文件中的C#中的一种方法。它不会在页面上呈现<text>元素。

Greeter.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方法可能令人困惑-它是RenderFragment<RenderFragment>,这意味着它必须是一个接受RenderFragment作为其唯一参数并返回{{1} }-在这种情况下返回的RenderFragment被标记包裹在RenderFragment中,以明确表示它是标记。

<text>

在这里尝试:https://blazorrepl.com/repl/QuPPaMEu34yA5KSl40