自定义元素不是 setting/getting 属性

custom elements not setting/getting attributes

如果我创建两个自定义元素并在另一个元素中创建其中一个,则属性不适用于第一个元素的新子元素。

class Bar extends HTMLElement {
  constructor() {
    super();
    const val = this.getAttribute('val') || 'no value';

    const shadow = this.attachShadow({mode: 'open'});
    const wrapper = document.createElement('div');
    wrapper.innerHTML = `
      <div class='bar'>
       <span>${val}</span>
      </div>
    `;

    shadow.appendChild(wrapper);
  }
}
customElements.define('x-bar', Bar);

class Foo extends HTMLElement {
  constructor() {
    super();
    const loop = this.getAttribute('loop') || 10;

    const shadow = this.attachShadow({mode: 'open'});
    const wrapper = document.createElement('div');
    
    for(let i=0; i<loop; i++){
     const b = document.createElement('x-bar');
        b.setAttribute('val', `value #${i}`);
      
        wrapper.appendChild(b);
    }

    shadow.appendChild(wrapper);
  }
}
customElements.define('x-foo', Foo);
<x-foo loop='3'></x-foo>

我希望我的输出是

value #0

value #1

value #2

因为我是这样设置属性的b.setAttribute('val', value #${i});

但我得到了 3 倍 no value

关于为什么会这样的任何意见? and/or如何解决,谢谢!

构造函数被调用之前,您不会设置属性;注意日志记录:

class Bar extends HTMLElement {
  constructor() {
    super();
    const val = this.getAttribute('val') || 'no value';
    console.log("In constructor, val = " + val);

    const shadow = this.attachShadow({mode: 'open'});
    const wrapper = document.createElement('div');
    wrapper.innerHTML = `
      <div class='bar'>
       <span>${val}</span>
      </div>
    `;

    shadow.appendChild(wrapper);
  }
}
customElements.define('x-bar', Bar);

class Foo extends HTMLElement {
  constructor() {
    super();
    const loop = this.getAttribute('loop') || 10;

    const shadow = this.attachShadow({mode: 'open'});
    const wrapper = document.createElement('div');
    
    for(let i=0; i<loop; i++){
      console.log("Constructing...");
     const b = document.createElement('x-bar');
      console.log("Setting attribute");
        b.setAttribute('val', `value #${i}`);
      
        wrapper.appendChild(b);
    }

    shadow.appendChild(wrapper);
  }
}
customElements.define('x-foo', Foo);
<x-foo loop='3'></x-foo>

您需要将呈现逻辑移出构造函数,以便您可以考虑属性集 post-构造。也许通过覆盖 setAttribute:

class Bar extends HTMLElement {
  constructor() {
    super();

    const shadow = this.attachShadow({mode: 'open'});
    this.wrapper = document.createElement('div');

    this.render(); // Though calling methods from the constructor isn't ideal
    shadow.appendChild(this.wrapper);
  }
  
  setAttribute(name, value) {
    super.setAttribute(name, value);
    if (name === "val") {
      this.render();
    }
  }
  
  render() {
    const val = this.getAttribute('val') || 'no value';
    this.wrapper.innerHTML = `
      <div class='bar'>
       <span>${val}</span>
      </div>
    `;
  }
}
customElements.define('x-bar', Bar);

class Foo extends HTMLElement {
  constructor() {
    super();
    const loop = this.getAttribute('loop') || 10;

    const shadow = this.attachShadow({mode: 'open'});
    const wrapper = document.createElement('div');
    
    for(let i=0; i<loop; i++){
      console.log("Constructing...");
     const b = document.createElement('x-bar');
      console.log("Setting attribute");
        b.setAttribute('val', `value #${i}`);
      
        wrapper.appendChild(b);
    }

    shadow.appendChild(wrapper);
  }
}
customElements.define('x-foo', Foo);
<x-foo loop='3'></x-foo>

从构造函数中调用方法并不理想,不过,您可能需要 fiddle 一点

大多数人都不知道 Web 组件的构造函数规则:

官方文档如下:

https://w3c.github.io/webcomponents/spec/custom/#custom-element-conformance

总结为:

您的构造函数代码:

  • 必须将对 super() 的无参数调用作为构造函数中的第一个语句。
  • 构造函数中的任何地方都不能有 return 语句。
  • 不得调用 document.write() 或 document.open()。
  • 不得检查元素的属性。
  • 不得更改或添加任何属性或子项。

一般来说,构造函数应该用于设置初始状态和默认值,并设置事件侦听器和影子根。

总的来说我同意@T.J。 Crowder,但我会对 Bar 对象做一个小修改:

class Bar extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
  }
  
  static get observedAttributes() {
    // Indicate that we want to be notified
    // when the `val` attribute is changed
    return ['val'];
  }
  
  connectedCallback() {
    // Render the initial value
    // when this element is placed into the DOM
    render(this.getAttribute('val'));
  }
  
  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal != newVal) {
      // If the value for the `val` attribute has changed
      // then re-render this element
      render(newVal);
    }
  }  
  
  render(val = 'no value') {
    this.shadowRoot.innerHTML = `
      <div class='bar'>
       <span>${val}</span>
      </div>
    `;
  }
}

customElements.define('x-bar', Bar);

这里使用了attributeChangedCallbackobservedAttributes的标准。虽然覆盖 setAttribute 功能有效,但它不是面向未来的。如果 setAttribute 的 API 将来要更改,那么您需要记住修复您的组件。对许多组件执行此操作会增加开发人员的大量债务。

class Bar extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    // Render the blank DOM
    this.shadowRoot.innerHTML = '<div class="bar"><span>no value</span><div>';
    this._span = this.shadowRoot.querySelector('span');
  }
  
  static get observedAttributes() {
    // Indicate that we want to be notified
    // when the `val` attribute is changed
    return ['val'];
  }
  
  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal != newVal) {
      // If the value for the `val` attribute has changed
      // then insert the value into the `<span>`
      this._span.textContent = newVal || 'no value';
      // OR: this._span.innerHTML = newVal || 'no value';
      // But make sure someone has not tried to hit you
      // with a script attack.
    }
  }
  
  get val() {
    return this._span.textContent;
  }
  set val(newVal) {
    if (newVal == null || newVal === false || newVal === '') {
      this.removeAttribute('val');
    }
    else {
      this.setAttribute('val', newVal);
    }
  }
}

customElements.define('x-bar', Bar);

第二种方法没有重新呈现整个 DOM 它只是将修改后的属性值插入到 <span> 标记中。

它还提供属性,因此您可以通过属性以及通过 JavaScript:

设置值
var el = document.querySelector('x-bar');
if (el) {
  el.val = "A New String";
  setTimeout(()=>el.val = '';,2000);
}