在 web component 中动态添加元素

Dynamically add elements inside web component

我想创建一个 Web 组件,其中包含可添加的元素列表。 例如,如果我有一个像这样的初始模板:

const template = document.createElement("template");
template.innerHTML = `<input type="text"></input><button>add div</button>`;

class MyElement extends HTMLElement {
  constructor() {
    super();
    this._shadowRoot = this.attachShadow({ mode: "open" });
    this._shadowRoot.appendChild(template.content.cloneNode(true));
    const button = this._shadowRoot.querySelector("button");
    button.addEventListener("click", this.addDiv);
  }
  addDiv(e) {
    // ...
  }
}
customElements.define("my-element", MyElement);

并且每次单击该按钮时,都会添加一个 <div>,其中包含来自输​​入字段的文本,从而创建如下内容:

<input type="text"></input><button>add div</button>
<div>first text from input added</div>
<div>second text from input added</div>
...

在你的情况下,你不能在 Shadow DOM shadowRoot 属性 上使用 insertAjacentHTML() 因为 Shadow Root 没有实现 Element 接口。

使用绑定( this )

更好的解决方案是在 shadowRoot 属性 上使用 appendChild()。但是,您需要在 click 事件回调中添加一个特殊的 bind() 操作。

在事件回调中,this 确实引用了触发事件的元素,而不是定义回调的对象。

为了获取对自定义元素的引用(为了访问 Shadow DOM shadowRoot 中的输入元素,在 addEventListener() 中调用 bind(this)

button.addEventListener( "click", this.addDiv.bind( this ) )

查看下面的完整示例:

const template = document.createElement("template");
template.innerHTML = `<input type="text"></input><button>add div</button>`;

class MyElement extends HTMLElement {
  constructor() {
    super();
    this._shadowRoot = this.attachShadow({ mode: "open" });
    this._shadowRoot.appendChild(template.content.cloneNode(true));
    const button = this._shadowRoot.querySelector("button");
    button.addEventListener("click", this.addDiv.bind( this ) );
  }
  addDiv(e) {
    var div = document.createElement( 'div' )
    div.textContent = this.shadowRoot.querySelector( 'input' ).value
    this.shadowRoot.appendChild( div )
  }
}
customElements.define("my-element", MyElement);
<my-element></my-element>


使用箭头函数

另一种解决方案是使用 arrow function。使用箭头函数,这不会重新定义,因此您不需要使用 bind().

class MyElement extends HTMLElement {
  constructor() {
    super()
    const sh = this.attachShadow( { mode: "open" } )
    sh.innerHTML = `<input type="text"></input><button>add div</button>`
    const button = sh.querySelector( "button" )
    button.onclick = ev => {
      let div = document.createElement( "div" )
      div.textContent = sh.querySelector( "input" ).value
      sh.appendChild( div )
    }
  }
}
customElements.define( "my-element", MyElement )
<my-element></my-element>

扩展 Supersharps 答案:

  • attachShadow() 设置 this.shadowRoot默认
  • attachShadow() returns shadowRoot,所以你可以在其上链接 .innerHTML
  • appendChild() returns 附加的 DIV,所以你可以链接它

  class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"})
        .innerHTML = '<input type="text"></input><button>add div</button>';
    this.shadowRoot.querySelector('button').onclick = evt =>
      this.shadowRoot
          .appendChild(document.createElement("div"))
          .innerHTML = this.shadowRoot.querySelector("input").value
  }
}
customElements.define("my-element", MyElement)
<my-element></my-element>

或者改写成一个辅助函数$append
这使得其余代码可读

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"});

    let $append = ( tag, html = '' ) => (
          tag = this.shadowRoot.appendChild(document.createElement(tag)),
          tag.innerHTML = html,
          tag // return tag, so onclick can be chained
        );

    let input = $append('input');
    $append('button', 'add div').onclick = evt => $append("div", input.value);
  }
}
customElements.define("my-element", MyElement)
<my-element></my-element>

感谢 Danny 的回答,我意识到我不需要创建 this._shadowRoot,所以我的问题和 Supersharp 的回答可以简化为以下内容。我保留了模板,因为从模板创建 Web 组件是一种很好的做法,因为性能比使用 shadowRoot.innerHTML.

更好
const template = document.createElement("template");
template.innerHTML = `<input type="text"></input><button>add div</button>`;

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    const button = this.shadowRoot.querySelector("button");
    button.addEventListener("click", this.addDiv.bind(this));
  }
  addDiv(e) {
    const div = document.createElement("div");
    div.textContent = this.shadowRoot.querySelector("input").value;
    this.shadowRoot.appendChild(div);
  }
}
customElements.define("my-element", MyElement);