将 shadow DOM 附加到自定义元素会消除错误,但为什么呢? [英] Attaching shadow DOM to a custom element removes error, but why?

查看:59
本文介绍了将 shadow DOM 附加到自定义元素会消除错误,但为什么呢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据自定义元素规范,

元素不得获得任何属性或子元素,因为这违反了使用 createElementcreateElementNS 方法的消费者的期望.

The element must not gain any attributes or children, as this violates the expectations of consumers who use the createElement or createElementNS methods.

在这种情况下,Firefox 和 Chrome 都正确地抛出了错误.但是,在附加 shadow DOM 时,不会出现错误(在任一浏览器中).

Both Firefox and Chrome correctly throw an error in this situation. However, when attaching a shadow DOM, no error is present (in either browser).

火狐:

NotSupportedError:不支持操作

NotSupportedError: Operation is not supported

铬:

未捕获的 DOMException:无法构造CustomElement":结果不能有子元素

Uncaught DOMException: Failed to construct 'CustomElement': The result must not have children

没有shadow DOM

function createElement(tag, ...children) {
  let root;

  if (typeof tag === 'symbol') {
    root = document.createDocumentFragment();
  } else {
    root = document.createElement(tag);
  }

  children.forEach(node => root.appendChild(node));

  return root;
}

customElements.define(
  'x-foo',
  class extends HTMLElement {
    constructor() {
      super();

      this.appendChild(
        createElement(
          Symbol(),
          createElement('div'),
        ),
      );
    }
  },
);

createElement('x-foo');

使用 shadow DOM

With shadow DOM

function createElement(tag, ...children) {
  let root;

  if (typeof tag === 'symbol') {
    root = document.createDocumentFragment();
  } else {
    root = document.createElement(tag);
  }

  children.forEach(node => root.appendChild(node));

  return root;
}

customElements.define(
  'x-foo',
  class extends HTMLElement {
    constructor() {
      super();

      // it doesn't matter if this is open or closed
      this.attachShadow({ mode: 'closed' }).appendChild(
        createElement(
          Symbol(),
          createElement('div'),
        ),
      );
    }
  },
);

createElement('x-foo');

请注意:要查看示例,您需要使用 (至少)以下之一:Firefox 63、Chrome 67、Safari 10.1.不支持 Edge.

Please note: in order to view the examples, you need to be using (at least) one of the following: Firefox 63, Chrome 67, Safari 10.1. Edge is not supported.

我的问题如下:

根据规范,行为是否正确?

Is the behavior demonstrated correct, per the specification?

向根添加子节点会导致DOM回流;如果不存在影子 DOM,如何避免这种情况?

Adding a child node to the root would cause a DOM reflow; how can this be avoided without a shadow DOM present?

推荐答案

每次创建元素都是通过构造函数完成的.但是,当构造函数被调用时,没有子元素也没有任何属性,这些都是在组件创建后添加的.

Every time an element is created it is done through the constructor. But, when the constructor is called there are no children nor any attributes, Those are all added AFTER the component is created.

即使元素是在 HTML 页面中定义的,它仍然是由代码使用构造函数创建的,然后通过解析 HTML 页面中的 DOM 的代码添加属性和子元素.

Even if the element is defined in the HTML page, it is still created by code using the constructor and then the attributes and children are added by the code that is parsing the DOM in the HTML page.

当构造函数被调用时,没有子元素,你不能添加它们,因为 DOM 解析器可能会在构造函数完成后立即添加它们.相同的规则适用于属性.

When the constructor is called there are no children and you can not add them since the DOM parser may be adding them as soon as the constructor is finished. The same rule applies for the attributes.

目前没有办法指定 shadowDOM 或 shadowDOM 子节点,除非通过 JS 代码.DOM 解析器不会向 shadowDOM 添加任何子元素.

Currently there is no way to specify shadowDOM or shadowDOM children except through JS code. The DOM parser will not add any children to the shadowDOM.

因此,根据规范,在构造函数中访问、更改或对属性或子项进行任何操作都是非法的.但是,由于 DOM 解析器无法向组件 shadowDOM 中添加任何非法内容.

So according to the spec it is illegal to access, change or do anything with attributes or children in the constructor. But, since there is no way for the DOM parser to add anything into a components shadowDOM that is not illegal.

通过使用在构造函数中创建的内部模板元素,然后在调用 connectedCallback 后将其放置为子元素,我在不使用 shadowDOM 时解决了这个问题.

I have gotten around this problem when not using shadowDOM by using an internal template element that is created in the constructor and then placed as a child once the connectedCallback is called.

// Class for `<test-el>`
class TestEl extends HTMLElement {
  constructor() {
    super();
    console.log('constructor');
    const template = document.createElement('template');
    template.innerHTML = '<div class="name"></div>';
    this.root = template.content;
    this.rendered = false;
  }

  static get observedAttributes() {
    return ['name'];
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal !== newVal) {
      console.log('attributeChangedCallback', newVal);
      this.root.querySelector('.name').textContent = newVal;
    }
  }

  connectedCallback() {
    console.log('connectedCallback');
    if (!this.rendered) {
      this.rendered = true;
      this.appendChild(this.root);
      this.root = this;
    }
  }

  // `name` property
  get name() {
    return this.getAttribute('name');
  }
  set name(value) {
    console.log('set name', value);
    if (value == null) { // Check for null or undefined
      this.removeAttribute('name');
    }
    else {
      this.setAttribute('name', value)
    }
  }
}

// Define our web component
customElements.define('test-el', TestEl);

const moreEl = document.getElementById('more');
const testEl = document.getElementById('test');
setTimeout(() => {
testEl.name = "Mummy";
  const el = document.createElement('test-el');
  el.name = "Frank N Stein";
  moreEl.appendChild(el);
}, 1000);

<test-el id="test" name="Dracula"></test-el>
<hr/>
<div id="more"></div>

这段代码在构造函数中创建了一个模板,并使用 this.root 来引用它.调用 connectedCallback 后,我将模板插入 DOM 并将 this.root 更改为指向 this 以便我对元素的所有引用仍然工作.

This code creates a template in the constructor and uses this.root to reference it. Once connectedCallback is called I insert the template into the DOM and change this.root to point to this so that all of my references to elements still work.

这是一种快速方法,可让您的组件始终保持其子组件正确,而无需使用 shadowDOM,并且仅在调用 connectedCalback 后才将模板作为子组件放入 DOM.

This is a quick way to allow your component to always be able to keep its children correct without using shadowDOM and only placing the template into the DOM as children once connectedCalback is called.

这篇关于将 shadow DOM 附加到自定义元素会消除错误,但为什么呢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆