【问题标题】:Custom element not picking up attributes自定义元素未获取属性
【发布时间】:2020-12-13 07:27:45
【问题描述】:

我正在尝试查看自定义元素以及它们是如何工作的,虽然 MDN 上的示例运行良好,但我似乎无法自己复制它们。

MDN 文章是here

This 是来自 MDN 的一个工作示例。

我的问题是我似乎永远无法将属性传递给我的组件,它们总是以 null 的形式出现,而不是传递参数的值。

我的 JS 是 (test.js)

  class PopUpInfo extends HTMLElement {
    constructor() {
      // Always call super first in constructor
      super();
  
      // Create a shadow root
      const shadow = this.attachShadow({mode: 'open'});
  
      // Create spans
      const wrapper = document.createElement('span');
      const info = document.createElement('span');
 
      // Take attribute content and put it inside the info span
      const text = this.getAttribute('foo'); // <-- this always returns null
      info.textContent = `(${text})`;

      shadow.appendChild(wrapper);
      wrapper.appendChild(info);
    }
  }
  
  // Define the new element
  customElements.define('popup-info', PopUpInfo);

还有我的 HTML:

<html>
    <head>
        <script src="test.js"></script>
    </head>
    <body>

        <hr>

        <popup-info foo="Hello World"></popup-info> 

        <hr>

    </body>
</html>

我希望在屏幕上看到的是文字

(你好世界)

但我所看到的只是

(空)

当我调试时,我可以看到 this.attributes 的长度为 0,因此它没有被传入。

有没有人在创建自定义元素时见过这种情况?

【问题讨论】:

标签: javascript html shadow-dom custom-element


【解决方案1】:

保持埃米尔的答案是正确的。

只是为了表明有可能的替代和更短的符号:

customElements.define('popup-info', class extends HTMLElement {
  static get observedAttributes() {
    return ['foo'];
  }

  constructor() {
    const wrapper = document.createElement('span');
    super().attachShadow({mode:'open'})// both SETS and RETURNS this.shadowRoot
           .append(wrapper);
    this.wrapper = wrapper;
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    switch(name) {
      case 'foo':
        this.wrapper.textContent = `(${newValue})`;
        break;
    }
  }
  
});
<popup-info 
  foo="Hello World"
  onclick="this.setAttribute('foo','Another world')"
  >
</popup-info>

【讨论】:

    【解决方案2】:

    虽然当我尝试在此处以 sn-p 运行您的示例时,它似乎运行良好,但我仍然想提出改进它的建议。

    使用observedAttributes 静态getter 来定义组件应该关注的属性列表。当某个属性的值已更改并且该属性的名称在列表中时,将调用attributeChangedCallback 回调。在那里,您可以断言每当您的属性值发生更改时该做什么的逻辑。

    在这种情况下,您可以构建您想要的字符串。这也有副作用,每当再次更改属性值时,都会更新字符串。

    class PopUpInfo extends HTMLElement {
    
      /**
       * Observe the foo attribute for changes.
       */
      static get observedAttributes() {
        return ['foo'];
      }
    
      constructor() {
        super();
    
        const shadow = this.attachShadow({
          mode: 'open'
        });
    
        const wrapper = document.createElement('span');
        const info = document.createElement('span');
        wrapper.classList.add('wrapper');
    
        wrapper.appendChild(info);
        shadow.appendChild(wrapper);
      }
      
      /**
       * Returns the wrapper element from the shadowRoot.
       */
      get wrapper() {
        return this.shadowRoot.querySelector('.wrapper')
      }
      
      /**
       * Is called when observed attributes have a changed value.
       */
      attributeChangedCallback(attrName, oldValue, newValue) {
        switch(attrName) {
          case 'foo':
            this.wrapper.textContent = `(${newValue})`;
            break;
        }
      }
      
    }
    
    // Define the new element
    customElements.define('popup-info', PopUpInfo);
    <html>
    
    <head>
      <script src="test.js"></script>
    </head>
    
    <body>
    
      <hr>
    
      <popup-info foo="Hello World"></popup-info>
    
      <hr>
    
    </body>
    
    </html>

    【讨论】:

    • 请注意,attributeChangedCallback 在连接元素之前被调用,因此您可能想要检查oldValue===null。请参阅 JSFidlle:jsfiddle.net/CustomElementsExamples/n20bwckt 以了解执行时间/执行内容的跟踪。但是一定要同时检查 FireFox 和 Chrome;有区别!
    • 嗨,丹尼,对答案的补充,绝对是要记住的事情。您的 jsfiddle 对自定义元素的生命周期和行为非常有洞察力。
    【解决方案3】:

    您在 HTML 中的脚本导入中缺少 defer 属性,并且无法正确加载,这就是问题所在。 defer 属性允许在页面被解析后执行脚本

    class PopUpInfo extends HTMLElement {
      constructor() {
        // Always call super first in constructor
        super()
    
        // Create a shadow root
        const shadow = this.attachShadow({ mode: 'open' })
    
        // Create spans
        const wrapper = document.createElement('span')
        const info = document.createElement('span')
    
        // Take attribute content and put it inside the info span
        const text = this.getAttribute('foo') // <-- this always returns null
    
        info.textContent = `(${text})`
    
        shadow.appendChild(wrapper)
        wrapper.appendChild(info)
      }
    }
    
    // Define the new element
    customElements.define('popup-info', PopUpInfo)
    <html>
      <head>
        <script src="app.js" defer></script>
      </head>
      <body>
        <hr />
    
        <popup-info foo="Hello World"></popup-info>
    
        <hr />
      </body>
    </html>

    【讨论】:

    • 这是一个完全错误的答案。可以在任何时间定义自定义元素。 defer 不是解决方案,而是 hack。给出的另一个答案是正确的。
    • @Danny'365CSI'Engelman 嗨,我真的不认为这是一个 hack,它只是对页面上脚本流的简单管理。甚至他展示的MDN example 也遵循与 defer 属性相同的逻辑。但我同意,如果他需要更强大的自定义元素实现,他可能想要遵循另一个答案中给出的解决方案。
    • 我不确定我们可以说它完全错误,MDN 页面显然是这样设置的 ref。但是我认为问题是这是否是最好的方法? MDN 的例子就是这样的事实确实给了它一些可信度。
    • 没有在那个 MDN 页面上说它使用defer 来加载脚本。重点是可以在之前之后使用来定义/升级Web组件。设置defer always 会升级组件late(大多数时候您不希望这样做,因为它会创建 FOUC)。它“解决”了你原来的问题;但这不是解决方案。这就像启动您的汽车,然后说它没有启动问题,因为它可以运行。
    • 我认为确切的问题是,MDN 页面上的任何地方都没有说要使用延迟来加载脚本,尽管他们显然需要它的示例。我发现 Google 文档现在好多了,而且解释的更清楚了。是的,您和 Emiel 的答案更好,现在已被接受。我怀疑需要做的实际上是对 MDN 文章的改进。
    猜你喜欢
    • 2019-05-24
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 2021-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多