哪些部分应该放在shadow DOM和light DOM中?

gui*_*ume 5 javascript dom web-component shadow-dom custom-element

我正在学习网络组件.在设计自定义元素时,我必须决定在shadow DOM中隐藏什么.然后将剩余部分暴露在轻型DOM中.

据我所知,API允许两个具有不同权衡的极端用例:

  • 在shadow DOM中几乎没有隐藏任何内容,元素的大部分内容都在light DOM和元素的属性中:
    • 这允许HTML作者在不编写JS的情况下为组件提供任何内容;
    • 这与可搜索性和可访问性的现状接近
    • 但所涉及的工作几乎没有回报; 我增加了组件的复杂性,但它们没有封装任何东西(一切都暴露出来).
  • 隐藏阴影DOM中的几乎所有内容,元素的innerHTML为空:
    • 这需要从JS实例化元素;
    • 这会更多地锁定用法,因为从JS实例化比使用HTML插槽和属性更严格(按类型);
    • 这可能不太容易搜索和访问(我不确定是否是这种情况);

我目前倾向于将所有内容隐藏在shadow DOM中,原因如下:

  • 我打算从JS实例化一切.我不打算用HTML手动创建页面.对HTML API和JS API进行编码会更加成功.
  • 隐藏一切的认知工作较少.我不需要找到关于哪些信息在light DOM中可见的正确平衡.
  • 它更接近我熟悉的大多数JS框架.

我错过了什么吗?


编辑

谢谢,我回答说这取决于部分回答我的问题的用例.但我仍然错过了关于我所处的案例的答案:我宁愿不支持某些组件的插槽.

我将为频谱的每个极端添加一个示例:

  • Light-DOM-heavy组件:组件用户必须将元素插入插槽

    <template id=light-email-view>
      <div>
        <div><slot name=from></slot></div>
        <ul><slot name=to></slot></ul>
        <h1><slot name=subject></slot></h1>
        <div><slot name=content></slot></div>
        <ul><slot name=attachements></slot></ul>
        <div class=zero-attachment-fallback>no attachments</div>
      </div>
    </template>
    
    Run Code Online (Sandbox Code Playgroud)

  • Shadow-DOM-heavy组件:组件用户必须使用JS API

    <template id=shadow-email-view>
      <div></div>
    </template>
    <script>
    ...
    let view = document.createElement('shadow-email-view');
    // this method renders the email in the shadow DOM entirely
    view.renderFromOject(email);
    container.appendChild(view);
    </script>
    
    Run Code Online (Sandbox Code Playgroud)

    在第一个示例中,组件作者需要做更多工作,因为他们需要"解析"DOM:他们必须计算附件以切换回退; 基本上,任何不是浏览器的输入转换都会将一个元素从light DOM复制到匹配的shadow DOM插槽中.然后他们需要监听属性变化等等.组件用户还有更多的工作,他们必须将正确的元素插入到正确的插槽中,其中一些非常重要(电子邮件内容可能必须链接).

    在第二个示例中,组件作者不需要实现支持从带有插槽的HTML实例化.但组件用户必须从JS实例化.所有渲染.renderFromObject都由组件作者在方法中完成.如果需要,一些其他方法提供钩子来更新视图.

    人们可以通过让组件提供插槽和JS助手来填补这些空白,从而提倡中间立场.但是,如果HTML作者不使用该组件,那么我没有看到这一点,而且还有更多工作要做.

    因此,使用影子DOM的所有东西都可行,或者我应该提供插槽,因为不这样做不符合标准,并且我的代码将在某些用户代理期待它们(忽略那些根本不了解自定义元素的旧UA) )?

  • Int*_*lia 6

    @supersharp 已经搞定了。

    我在 Web Components 中看到的一件事是,人们倾向于让他们的组件做的太多,而不是分解成更小的组件。

    让我们考虑一些原生元素:

    <form> 没有影子 DOM,它唯一能做的就是从其子表单元素中读取值,以便能够执行 HTTP GET、POST 等操作。

    <video>100% shadowDOM 并且它唯一使用应用程序提供的子项来定义将要播放的视频。用户不能为标签的阴影子项调整任何CSS <video>。也不应该允许他们这样做。<video>标签允许的唯一功能是隐藏或显示那些影子孩子。该<audio>标签做同样的事情。

    <h1><h6>没有影子。所有这些都是设置默认字体大小并显示子项。

    <img>标签使用阴影子项来显示图像和替代文本。

    就像@supersharp 所说的,shadowDOM 的使用是基于元素的。我会进一步说 shadowDOM 应该是一个经过深思熟虑的选择。我要补充的是,您需要记住这些应该是组件而不是应用程序。

    是的,您可以将整个应用程序封装到一个组件中,但浏览器并没有尝试使用 Native 组件来做到这一点。您可以使组件变得越专业,它们就变得越可重用。

    避免在您的 Web 组件中添加任何不是 vanilla JS 的内容,换句话说,不要在您的组件中添加任何框架代码,除非您不想与不使用该框架的人共享它们。我编写的组件是 100% Vanilla JS,没有 CSS 框架。它们在 Angular、React 和 vue 中使用,无需更改代码。

    而是选择了使用 shadowDOM 为每个组件编写。并且,如果您必须在原生不支持 Web 组件的浏览器中工作,那么您可能根本不想使用 shadowDOM。

    最后一件事。如果您编写的组件不使用 shadowDOM 但它有 CSS,那么您必须小心放置 CSS 的位置,因为您的组件可能会被放置到其他人的 shadowDOM 中。如果您的 CSS 被放​​置在<head>标签中,那么它将在另一个 shadowDOM 中失败。我使用此代码来防止该问题:

    function setCss(el, styleEl) {
      let comp = (styleEl instanceof DocumentFragment ? styleEl.querySelector('style') : styleEl).getAttribute('component');
      if (!comp) {
        throw new Error('Your `<style>` tag must set the attribute `component` to the component name. (Like: `<style component="my-element">`)');
      }
    
      let doc = document.head; // If shadow DOM isn't supported place the CSS in `<head>`
      // istanbul ignore else
      if (el.getRootNode) {
        doc = el.getRootNode();
        // istanbul ignore else
        if (doc === document) {
          doc = document.head;
        }
      }
    
      // istanbul ignore else
      if (!doc.querySelector(`style[component="${comp}"]`)) {
        doc.appendChild(styleEl.cloneNode(true));
      }
    }
    
    export default setCss;
    
    Run Code Online (Sandbox Code Playgroud)


    Sup*_*arp 5

    选择100%取决于用例.

    也:

    • 如果您希望用户能够使用全局CSS样式属性设置自定义元素的格式,您可以选择普通的轻量级DOM.

    • 你是对的:在Shadow DOM中,"这可能不太容易搜索":该document.querySelector()方法不会检查Shadow DOM内容.

    • 因此,一些第三方的JS库可能无法与Shadow DOM轻松集成

    • 如果您打算对旧版浏览器使用自定义元素polyfill,则可以避免使用Shadow DOM,因为它的某些功能无法实际填充.

    • 在许多情况下,答案是提供Light DOM和Shadow DOM的混合.正如@JaredSmith所建议的那样:

      • Web组件作者的Shadow DOM,
      • 用于Web Compoent用户的Light DOM,与Shadow DOM集成<slot>.

    作为结论,您应该考虑使用Web组件来确定是否需要Shadow DOM的上下文.


    回答编辑

    考虑到您的用例,我将创建一个自定义元素,并:

    • 让用户使用原子值填充light DOM:按@Intervalia的建议输入元素<div class="mail-to">或自定义子组件<mail-to>,
    • 使用Shadow DOM来掩盖光DOM,
    • 使用Javascript:this.querySelectorAll('.mail-to')或者this.querySelectorAll('mail-to')代替<slot>从轻型DOM中提取数据并将它们复制(或移动)到Shadow DOM.

    这样,用户就不必学习<slot>工作,开发人员将能够更自由地格式化Web组件呈现.

    <email-view>
      <mail-to>guillaume@stackoverflow.com</mail-to>
      <mail-to>joe@google.fr</mail-to>
      <mail-from>supersharp@cyber-nation.fr</mail-from>
      <mail-body>hello world!</mail-body>
    <email-view>
    
    Run Code Online (Sandbox Code Playgroud)