从 Web 组件内部访问 html 标签

Access to html tag from inside Web Components

我正在学习 js 网络组件的初步概念。我对这些很感兴趣,并尝试做一个简单的例子。我的组件只是一个假装将颜色更改为 div.

的按钮

我的示例工作符合我的预期,但我注意到我的方法不是太多的“组件方式”,如果我尝试更改的元素在我的 Web 组件中而不是在它之外。

这是我的 html 文件:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="index.css" />
  </head>
  <body>
    <div class="main">
      <h1>Yeah Web Components!</h1>
      <my-component></my-component>
    </div>

    <script src="myComponent.js"></script>
  </body>
</html>

这是我的组件.js:

const template = `
  <style>
    .container{
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    .box{
        width: 100px;
        height: 100px;
        background: red;
    }
    .hi-button{
      margin-top: 10px;
    }
    
  </style>
  <div class="container">
    <div class="box"></div>
    <button class="hi-button">Change</button>
  </div>
 
`;

class MyFirstTest extends HTMLElement{
  constructor(){
    super()
    const shadowRoot = this.attachShadow({ mode: 'open' }); 
    shadowRoot.innerHTML = template; 
  }

  changeButtonColor(){
      const box = this.shadowRoot.querySelector(".box");
      if(box.style.background === 'red'){
        box.style.background = 'blue';
      }else{
        box.style.background = 'red';
      }
  }

  connectedCallback(){
      const event = this.shadowRoot.querySelector(".hi-button");
      event.addEventListener('click', () => this.changeButtonColor());
  }

  disabledCallback(){
    const event = this.shadowRoot.querySelector(".button-test");
    event.removeEventListener();
  }

}

customElements.define('my-component', MyFirstTest);

正如我所说,功能运行良好,但我不希望我的 div 出现在我的 Web 组件中,而是出现在我的 html 文件中,并且mi组件只是按钮。

例如,我的 html 文件是这样的:

.......

  <link rel="stylesheet" href="index.css" />
  </head>
  <body>
    <div class="main">
      <h1>Yeah Web Components!</h1>
      <div class="my-div-to-change"></div>
      <my-component></my-component>
    </div>
  </body>

.......

网络组件可以这样工作吗?

保持组件之间独立性的最好方法是使用events

Web 组件调度由 parent 容器侦听的事件。然后,parent 执行必要的操作。

如果你想在组件和 parent 之间“直接”对话,系统耦合度很高,不是一个好方法。当然可以,但不推荐。

您可以查看答案,哪个更清楚。

此外,使用事件回答您的问题。就这么简单:

首先,在您的组件中,您必须以这种方式分派事件:

this.dispatchEvent(new CustomEvent("button-clicked", { 
    bubbles: true,
}));

你可以检查here这个的用法。

此外,parent 将使用以下方式收听:

document.addEventListener("button-clicked", changeColor);

因此,当单击按钮时,parent 将触发函数 changeColor,其中包含您想要的逻辑。

通过这种方式,您可以使用 Web 组件对 parent 容器执行操作,但系统并未耦合。 Parent 可以在没有 child 的情况下工作,child 可以在没有 parent 的情况下工作。两者都可以单独使用。当然,事件不会被派发或监听,但组件之间没有依赖关系。

这也是它如何工作的一个例子。

const template = `

  <div class="container">

    <button class="hi-button">Change</button>
  </div>
 
`;

class MyFirstTest extends HTMLElement{
  constructor(){
    super()
    const shadowRoot = this.attachShadow({ mode: 'open' }); 
    shadowRoot.innerHTML = template; 
  }

  changeButtonColor(){
      //Call parent
      this.dispatchEvent(new CustomEvent("button-clicked", { 
            bubbles: true,
        }));
  }

  connectedCallback(){
      const event = this.shadowRoot.querySelector(".hi-button");
      event.addEventListener('click', () => this.changeButtonColor());
  }

  disabledCallback(){
    const event = this.shadowRoot.querySelector(".button-test");
    event.removeEventListener();
  }

}

customElements.define('my-component', MyFirstTest);
<!DOCTYPE html>
<html lang="en">
  <head>
   <style>
    .container{
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    .box{
        width: 100px;
        height: 100px;
        background: red;
    }
    .hi-button{
      margin-top: 10px;
    }
    
  </style>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="index.css" />
  </head>
  <body>
    <div class="main">
      <h1>Yeah Web Components!</h1>
      <div class="box"></div>
      <my-component></my-component>
    </div>

    <script src="myComponent.js"></script>
    <script>
    //Add listener to do the action
    document.addEventListener("button-clicked", changeColor);
    function changeColor(){
      var box = document.querySelector(".box");
      if(box.style.backgroundColor === 'red'){
        box.style.backgroundColor = 'blue';
      }else{
        box.style.backgroundColor = 'red';
      }
    }
    </script>
  </body>
</html>

发布的代码有一些注意事项:

CSS & 阴影DOM

   .container{
      display: flex;
      flex-direction: column;
      align-items: center;
    }
   .hi-button{
      margin-top: 10px;
    }

global CSS 永远不会应用于元素阴影 HTML 内 DOM.

请注意,您不必使用影子DOM。

  • 模板
  • 自定义元素API
  • 阴影DOM

有 3 种不同的 技术,您可以单独使用它们。

要应用样式,请在阴影内部声明它DOM:

const template = `
  <style>
   .container{
      display: flex;
      flex-direction: column;
      align-items: center;
    }
   .hi-button{
      margin-top: 10px;
    }
  </style>
  <div class="container">
    <button class="hi-button">Change</button>
  </div>

模板

它只是一个字符串,您没有使用或声明 <template>

点击事件

按钮上的点击事件,默认情况下,弹出 DOM。

无需在 <button> 本身上设置处理程序,您可以在任何父级上设置。

垃圾回收

无需删除监听器 inside/on 自定义元素。 JavaScript 垃圾收集 进程会为您做这件事

在其他 DOM 元素上设置监听器时,需要移除监听器。
例如。当组件在 document

上设置监听器时

禁用回调

不存在;它叫做 disconnectedCallback()

组合 = 真

   this.dispatchEvent(new CustomEvent("button-clicked", { 
      bubbles: true,
   }));

这只有效,因为事件不是从影子DOM调度的。

但是从 shadowDOM 内部调度,或者用 shadowDOM 将元素包裹在另一个元素中,代码将不再有效,因为 Custom默认情况下,事件不会 'escape' shadowDOM(s).

为此,您需要使用:

   this.dispatchEvent(new CustomEvent("button-clicked", { 
      bubbles: true,
      composed: true
   }));

注意:默认click do escape shadowDOM这样的事件默认

解决方案

没有阴影DOM唯一需要的代码是:

  • 全局 CSS 现在设置按钮样式

customElements.define('my-component', class MyFirstTest extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `<button class="hi-button">Change</button>`;
    this.onclick = (evt) => document.querySelector(".box").classList.toggle("blue");
  }
});
<style>
  .box {
    width: 80px;
    height: 80px;
    background: red;
  }
  
  .blue {
    background: blue;
  }
  
  .hi-button {
    margin-top: 10px;
  }
</style>
<div class="main">
  <h1>Yeah Web Components!</h1>
  <div class="box"></div>
  <my-component></my-component>
</div>

有阴影DOM

  • 不需要CSS类,因为所有HTML都被隔离在阴影DOM

  • <template> 可以 声明为 HTML(所以你的 IDE 可以整齐地格式化里面的所有内容)由 this.nodeName,所以你可以重复使用代码

  • super()&attachShadow()是函数设置返回 this 范围和 this.shadowRoot;所以他们可以被链接起来

  • 自定义事件很棒;但是这段代码可以处理默认的 click 事件;侦听器检查是否单击了正确的按钮

  • 这些只是 Web Component 技术增强,加载更多功能增强可能,完全取决于用例

  • 在继续阅读 <slot> 内容之前请先阅读

代码可能看起来类似

<template id="MY-COMPONENT">
  <style>
    div { display: flex; flex-direction: column; align-items: center }
    button { margin-top: 10px }
  </style>
  <div>
    <button><slot></slot></button>
  </div>
</template>
<div class="main">
  <my-component color="blue">Blue!</my-component>
  <my-component color="gold">Gold!</my-component>
  <my-component color="rebeccapurple">Purple!</my-component>
</div>
<script>
  document.addEventListener("click", (evt) => {
    if (evt.target.nodeName == 'MY-COMPONENT')
      document.querySelector(".main").style.background = evt.target.getAttribute("color");
  });

  customElements.define('my-component', class extends HTMLElement {
    constructor() {
      let template = (id) => document.getElementById(id).content.cloneNode(true)
      super()
        .attachShadow({mode: 'open' })
        .append( template(this.nodeName) );
    }
  });
</script>