我需要在用户提供的内容中动态放置Blazor组件。从本质上讲,该组件应该使用一些UI元素来扩展用户提供的标记。
假设用户提供一些内容;问候容器";元素,组件应该在该元素中插入一个问候按钮。
我目前的解决方案是调用一个JavaScript函数来移动OnAfterRenderAsync
中的DOM元素(完整代码如下(。它似乎工作得很好,但在Blazor中似乎不鼓励操作DOM元素,因为它会影响困难的算法。所以我有几个问题:
- 像这样移动DOM元素有多糟糕?它是否会导致性能问题、功能问题或某些未定义的行为
- 有没有更好的方法可以在不使用JavaScript的情况下实现相同的结果?我曾考虑使用
RenderTreeBuilder
,但它似乎不是为此目的而设计的,因为建议使用硬编码序列号,这在处理编译时未知的动态内容时似乎是不可能的
当前解决方案代码:
贪婪剃刀
@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.rarzor
@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>
好问题-是的,使用JS移动dom元素非常糟糕,因为Blazor没有看到您对dom所做的更改。
您可以做的是切换到使用RenderFragment
,更具体地说,RenderFragment<RenderFragment>
是一种标记,它将提供更多标记作为参数。
在第二行中,我调用UserContentMarkup方法(它是RenderFragment<RenderFragment>
(,并传入<div id=greeting>
内容作为context
参数。
注意:它被封装在<text>
元素中,这实际上是将C#中的HTML嵌入Razor文件的一种方式。它不会向页面呈现<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
作为其唯一参数并返回RenderFragment
的方法——在这种情况下返回的RenderFragment
是用<text>
包装的标记,以表明它是标记。
@code
{
RenderFragment<RenderFragment> userContent
=> context => @<text>Some stuff @context more stuff</text>;
}
在这里试试:https://blazorrepl.com/repl/QuPPaMEu34yA5KSl40