自定义元素:属性不会更新组件状态

custom element: attribute won't update component state

我是 Web 组件的新手,在 html 中编写组件并直接在 html 中添加属性时遇到问题。 问题是组件没有触发 set 属性 函数。

仅当我使用 javascript 设置属性或创建组件并将其添加到 DOM 时,set 属性 函数才有效。

我创建了一支笔来举例说明我的问题:

Go to Pen

window.addEventListener('load', () => {
  document.body.getElementsByTagName('news-article')[1].article = {
    title: 'dynamic value'
  };

  let element = document.createElement('news-article');
  element.article = {
    'title': 'dynamic element'
  };

  document.body.appendChild(element);
})

class NewsArticle extends HTMLElement {

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

  constructor() {
    debugger
    super();
    this.root = this.attachShadow({
      mode: 'open'
    });
  }

  set article(val) {
    debugger
    this.root.innerHTML = `
      <style>
       :host {
        display: block;
        border: 3px solid #000;
        padding: 15px;
       }

       h2 {
        text-transform: uppercase;
       }
      </style>


      <h2>${val.title}</h2>
     `;
  }

  get article() {
    debugger
    return this.getAttribute('article');
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    debugger
    this.setAttribute(attrName) = JSON.parse(newVal);
  }
}

window.customElements.define('news-article', NewsArticle);
<news-article article="{ title: 'static value' }"></news-article>
<news-article></news-article>

setAttribute 接受两个参数,而不是你 JSON.parse(newVal) 分配给任何 this.setAttribute(attrName) returns (我假设 undefined).

this.setAttribute(attrName) = JSON.parse(newVal);

必须是

this.setAttribute(attrName, JSON.parse(newVal));

除此之外,请注意 { title: 'static value' } 无效 JSON。您不能使用单引号来引用键或值。必须是双引号。

接下来,在您的 attributeChangedCallback 中执行 this.setAttribute(attrName, JSON.parse(newVal)) 没有意义,原因有二:

  1. 该属性已经在被设置为该属性的过程中(这就是为什么您的 attributeChangedCallback 正在被执行)
  2. 属性只能包含 String 个值。

相反,你想要做的是

this.article = JSON.parse(newVal);

这将触发您的 getter(这是您想要的,因为它才是真正更新您的组件的)。

我假设您的误解是由于您假设自定义属性自动与同名 属性 同步 - 但并非如此

window.addEventListener('load', () => {
  document.body.getElementsByTagName('news-article')[1].article = {
    title: 'dynamic value'
  };

  let element = document.createElement('news-article');
  element.article = {
    'title': 'dynamic element'
  };

  document.body.appendChild(element);
})

class NewsArticle extends HTMLElement {

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

  constructor() {
    super();
    this.attachShadow({
      mode: 'open'
    });
  }

  set article(val) {
    this.shadowRoot.innerHTML = `
<style>
  :host {
    display: block;
    border: 3px solid #000;
    padding: 15px;
  }

  h2 {
    text-transform: uppercase;
  }
</style>


<h2>${val.title}</h2>`;
     
  }

  get article() {
    return this.getAttribute('article');
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    this.article = JSON.parse(newVal);
  }
}

window.customElements.define('news-article', NewsArticle);
<news-article article='{ "title": "static value" }'></news-article>
<news-article></news-article>
<news-article article='{ "title": "static value" }'></news-article>

@connexo 对属性发生了什么、如何操作它们以及如何提取它们的值给出了很好的解释。

我想通过稍微更改结构来添加他的解决方案。

Getters 和 setters: 使用 getters 和 setters 来操作元素的属性。这样你就可以通过使用news-article.article = { title: 'Breaking news' }改变属性值和HTML来改变article属性,并使用news-article.article得到当前值article 属性。

因为您正在观察 article 属性,所以当您更改 article 属性值时,它会触发 attributeChangedCallback。你应该把你的逻辑放在那里改变一切 属性的值。在您的情况下,更改阴影 DOM.

innerHTML
class NewsArticle extends HTMLElement {

  /**
   * Fire the attributeChangedCallback when the article
   * attribute has been changed.
   */
  static get observedAttributes() {
    return ['article'];
  }

  constructor() {
    super();
    this.attachShadow({
      mode: 'open'
    });
  }

  /** 
   * Set the article attribute value.
   *
   * This will fire the attributeChangedCallback because
   * 'article' is in the observedAttributes array.
   */
  set article(val) {
    this.setAttribute('article', JSON.stringify(val));
  }

  /** 
   * Gets the current article attribute value.
   */
  get article() {
    return JSON.parse(this.getAttribute('article'));
  }

  /** 
   * Do something when an attribute is changed.
   *
   * In this case change the innerHTML of the shadowRoot
   * when the 'article' attribute has changed.
   */
  attributeChangedCallback(attrName, oldVal, newVal) {
    if (attrName === 'article') {
      const { title } = JSON.parse(newVal);
      this.shadowRoot.innerHTML = `
        <style>
          :host {
            display: block;
            border: 3px solid #000;
            padding: 15px;
          }

          h2 {
            text-transform: uppercase;
          }
         </style>
         <h2>${title}</h2>`;
    }
  }

}

感谢@connexo 的出色工作!