规范中提到的自定义元素示例的最小实现是什么?

What is the minimal implementation for custom elements example mentioned in the specifications?

https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-autonomous-example:htmlelement

在规范中,他们提供了 创建自主自定义元素 的示例。但是,他们为读者留下了 _updateRendering() 方法实现。

class FlagIcon extends HTMLElement {
  constructor() {
    super();
    this._countryCode = null;
  }

  static observedAttributes = ["country"];

  attributeChangedCallback(name, oldValue, newValue) {
    // name will always be "country" due to observedAttributes
    this._countryCode = newValue;
    this._updateRendering();
  }
  connectedCallback() {
    this._updateRendering();
  }

  get country() {
    return this._countryCode;
  }
  set country(v) {
    this.setAttribute("country", v);
  }

  _updateRendering() {
    // Left as an exercise for the reader. But, you'll probably want to
    // check this.ownerDocument.defaultView to see if we've been
    // inserted into a document with a browsing context, and avoid
    // doing any work if not.
  }
}

已提出问题以提供剩余的实现,以便更好地理解主题并快速继续。
问题:https://github.com/whatwg/html/issues/3029

我们可以放什么代码来获得所需的功能?

这是实现相同要求的完整代码:

<!DOCTYPE html>
<html lang="en">
  <body>
    <flag-icon country="in"></flag-icon><br>
    <flag-icon country="nl"></flag-icon><br>
    <flag-icon country="us"></flag-icon><br>
  </body>

  <script>
    class FlagIcon extends HTMLElement {
      constructor() {
        super();
        this._countryCode = null;
      }

      static observedAttributes = ["country"];

      attributeChangedCallback(name, oldValue, newValue) {
        // name will always be "country" due to observedAttributes
        this._countryCode = newValue;
        this._updateRendering();
      }
      connectedCallback() {
        this._updateRendering();
      }

      get country() {
        return this._countryCode;
      }
      set country(v) {
        this.setAttribute("country", v);
      }

      _updateRendering() {
        //**remaining code**
        if (this.ownerDocument.defaultView && !this.hasChildNodes()) {
          var flag = document.createElement("img");
          flag.src = "https://flagcdn.com/24x18/" + this._countryCode + ".png";
          this.appendChild(flag);
        }
      }
    }
    customElements.define("flag-icon", FlagIcon);
  </script>
</html>

注意:加载图片可能需要一些时间,具体取决于网速。

如果我遗漏了什么,请告诉我。

您的解决方案失败,一旦设置了标志,您将永远无法更改该值。

那个“练习”很老了.. 很老了.. 并且试图展示自定义元素可以做的一切。

这是完全错误的.. 关键是当 attributeChanged 运行时,old/new 值是什么

并且 attributeChangedCallback 运行 BEFORE connectedCallback;这就是添加 https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected 的原因。

您的代码在 attributeChangedCallback 中获取了 3 个参数,但您不能对它们执行任何操作,因为执行总是转到 _updateRendering 方法。

如果练习的目的是在观察到 attributes 变化时学习,我会使用:

代码也可以在 JSFiddle 中找到:https://jsfiddle.net/dannye/43ud1wvn/

<script>
  class FlagIcon extends HTMLElement {
    static observedAttributes = ["country"];
    log(...args) {
      document.body.appendChild(document.createElement("div"))
              .innerHTML = `${this.id} - ${args.join` `}`;
    }
    attributeChangedCallback(name, oldValue, newValue) {
      this.log("<b>attributeChangedCallback:</b>", `("${name}" , "${oldValue}", "${newValue}" )`);
      if (this.isConnected) {
        if (newValue == oldValue) this.log(`Don't call SETTER ${name} again!`);
        else this[name] = newValue; // call SETTER
      } else this.log("is not a DOM element yet!!!");
    }
    connectedCallback() {
      this.log("<b>connectedCallback</b>, this.img:", this.img || "not defined");
      this.img = document.createElement("img");
      this.append(this.img); // append isn't available in IE11
      this.country = this.getAttribute("country") || "EmptyCountry";
    }
    get country() { // the Attribute is the truth, no need for private variables
      return this.getAttribute("country");
    }
    set country(v) {
      this.log("SETTER country:", v);
      // Properties and Attributes are in sync, 
      // but setAttribute will trigger attributeChanged one more time!
      this.setAttribute("country", v);
      if (this.img) this.img.src = `//flagcdn.com/20x15/${v}.png`;
      else this.log("can't set country", v);
    }
  }
  customElements.define("flag-icon", FlagIcon);

  document.body.onclick = () => {
    flag1.country = "nl";
    flag2.setAttribute("country", "nl");
  }
</script>

<flag-icon id="flag1" country="in"></flag-icon><br>
<flag-icon id="flag2" country="us"></flag-icon><br>

这只是一种方法,这完全取决于 what/when/how 您的自定义元素需要进行更新。

定义 CustomElement 时也很重要; beforeafter DOM 被解析。大多数开发人员只是在他们的脚本上敲打 deferredmethod,而不理解它意味着什么。

始终使用定义自定义元素之前的代码测试您的 Web 组件,它在 DOM 中使用。

真实世界<flag-icon> Web 组件

待优化:

<script>
  customElements.define("flag-icon", class extends HTMLElement {
    static observedAttributes = ["country"];
    attributeChangedCallback() {
      this.isConnected && this.connectedCallback();
    }
    connectedCallback() {
      this.img = this.img || this.appendChild(document.createElement("img"));
      this.img.src = `//flagcdn.com/120x90/${this.country}.png`;
    }
    get country() {
      return this.getAttribute("country") || console.error("Missing country attribute",this);
    }
    set country(v) {
      this.setAttribute("country", v);
    }
  });
</script>

<flag-icon id="flag1" country="gb"></flag-icon>
<flag-icon id="flag2" country="eu"></flag-icon>

或者根本没有外部图像

使用 FlagMeister Web Component 创建所有 SVG 客户端

<script src="//flagmeister.github.io/elements.flagmeister.min.js"></script>

<div style="display:grid;grid-template-columns: repeat(3,1fr);gap:1em">
  <flag-jollyroger></flag-jollyroger>
  <flag-un></flag-un>
  <flag-lgbt></flag-lgbt>
</div>