【问题标题】:Position Blazor component inside user content在用户内容中定位 Blazor 组件
【发布时间】:2021-02-20 04:27:52
【问题描述】:

我需要动态地将 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>

【问题讨论】:

    标签: asp.net-core blazor blazor-server-side asp.net-blazor


    【解决方案1】:

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

    您可以做的是切换到使用RenderFragment,更具体地说是RenderFragment&lt;RenderFragment&gt;,这是一个将提供更多标记作为参数的标记。

    在第二行,我正在调用 UserContentMarkup 方法(这是一个RenderFragment&lt;RenderFragment&gt;)并将&lt;div id=greeting&gt; 内容作为context 参数传入。

    注意:它包含在 &lt;text&gt; 元素中,这实际上是一种将 HTML 嵌入到 Razor 文件中的 C# 中的方法。它不会向页面呈现&lt;text&gt; 元素。

    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&lt;RenderFragment&gt;,这意味着它必须是一个接受RenderFragment 作为其唯一参数并返回一个RenderFragment 的方法——返回的是RenderFragment在这种情况下,标记包裹在 &lt;text&gt; 中,以明确它是标记。

    @code
    {
        RenderFragment<RenderFragment> userContent 
            => context => @<text>Some stuff @context more stuff</text>;
    }
    

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

    【讨论】:

    • 谢谢!这看起来几乎正是我所需要的。唯一的额外问题是,这个字符串“Some stuff @context more stuff”能否像从数据库中加载一样动态提供?因为它是我们事先不知道的用户内容
    • 是的,但是你需要一个占位符来表示上下文并在运行时替换它
    • 有没有这样做的例子?从文档中我发现似乎所有标记都必须在编译时提供,因此“Blazor 编译器”可以将其翻译成 html。但是如果有什么方法可以动态获取它,那就太好了
    • 我猜你的意思是有一个占位符作为上下文,并在组件渲染后通过 JavaScript 替换它?
    • 没有JS就不行。但是为 pre 和 post 设置两个 RenderFragment 可能会更容易
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-17
    • 2020-06-01
    • 2022-09-28
    • 2021-09-10
    • 1970-01-01
    相关资源
    最近更新 更多