当我将 SCEditor 添加到我的 Blazor 项目时,编辑器总是出现在奇怪的地方,有时会出现多个副本。我该如何解决?

When I add SCEditor to my Blazor project, the editor keeps appearing in strange places, sometimes in multiple copies. How do I fix this?

我想在我的 Blazor 页面中使用 SCEditor

例如,我创建了一个新的 Blazor WASM 项目并执行了以下步骤:

  1. 根据文档,我将此代码和引用添加到 index.html:

    window.InitEditor = () => {
        var textarea = document.getElementById('myTextArea');
        sceditor.create(textarea, {
            format: 'bbcode',
            style: 'https://cdn.jsdelivr.net/npm/sceditor@3/minified/themes/content/default.min.css'
        });
    };
    
  2. counter页面添加textarea:

    <textarea id="myTextArea" style="width:100%; height:200px;"></textarea>
    
  3. 将以下代码添加到 counter 页面的代码部分:

    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        JsRuntime.InvokeVoidAsync("InitEditor");
        return base.OnAfterRenderAsync(firstRender);
    }
    

现在我 运行 这个项目,我看到了这个编辑器:

然后我点击获取数据菜单,我看到了这个:

令人惊讶的是,编辑器已显示在获取数据页面中。如果我再次点击计数器页面,我会看到:

有 2 位编辑 O-O。如果我点击这个菜单,那么编辑器就会增加...

我这样修改了代码:

protected override Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
        JsRuntime.InvokeVoidAsync("InitEditor");
    return base.OnAfterRenderAsync(firstRender);
}

编辑器在 counter 页面和 fetch data 页面显示一次。同样,我在 fetch data 页面中没有任何 textarea

我该如何解决这个问题?

Blazor 文档 warns:

Only mutate the Document Object Model (DOM) with JavaScript (JS) when the object doesn't interact with Blazor. Blazor maintains representations of the DOM and interacts directly with DOM objects. If an element rendered by Blazor is modified externally using JS directly or via JS Interop, the DOM may no longer match Blazor's internal representation, which can result in undefined behavior. Undefined behavior may merely interfere with the presentation of elements or their functions but may also introduce security risks to the app or server.

This guidance not only applies to your own JS interop code but also to any JS libraries that the app uses, including anything provided by a third-party framework, such as Bootstrap JS and jQuery.

SCEditor 正是那些 DOM 变异库之一,您可以亲眼看到未能遵守该指南的影响。 (“安全风险”这一点相当荒谬:如果仅通过修改客户端代码就可以使您的应用程序变得不安全,那么它一开始就不是很安全。但这是一个很好的建议。)

Blazor 确实以 element references 的形式提供了一些与外部 DOM 突变的互操作性。文档再次警告:

Only use an element reference to mutate the contents of an empty element that doesn't interact with Blazor. This scenario is useful when a third-party API supplies content to the element. Because Blazor doesn't interact with the element, there's no possibility of a conflict between Blazor's representation of the element and the Document Object Model (DOM).

注意这个警告,你应该写下类似下面的内容(未经测试)。在组件文件中(.razor):

<div @ref="sceditorContainer"></div>

@inject IJSRuntime js

@code {

private ElementReference sceditorContainer;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    await base.OnAfterRenderAsync(firstRender);
    if (firstRender)
    {
        await js.InvokeVoidAsync("initEditor", sceditorContainer);
    }
}

}

在JavaScript中:

function initEditor(container) {
    const textarea = document.createElement('textarea');
    container.appendChild(textarea);
    sceditor.create(textarea, {
        format: 'bbcode',
        style: 'https://cdn.jsdelivr.net/npm/sceditor@3/minified/themes/content/default.min.css'
    });
}

如果 SCEditor 库运行良好,它最多只应在您提供的文本区域节点的父节点级别修改 DOM 树。您可能认为在您的组件标记中放置一个 <textarea> 并捕获对其的引用就足够了,但实际上 SCEditor 会向该节点添加同级节点,这可能会不断扰乱渲染。出于这个原因,将所有内容放在一个初始为空的包装元素中会更安全,它可以充当 SCEditor 可以自由发挥作用的沙箱。

理想情况下,您会将与 SCEditor 相关的所有内容封装到一个仅处理 SCEditor 的专用组件中,而您的应用程序的其余部分将像使用任何其他 Blazor 组件一样使用该组件。