【问题标题】:Access to html tag from inside Web Components从 Web 组件内部访问 html 标签
【发布时间】:2021-01-18 14:36:13
【问题描述】:

我正在学习 js Web 组件的第一个概念。我很喜欢这些,并尝试做一个简单的例子。我的组件只是一个假装将颜色更改为 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>

.......

Web 组件可以这样工作吗?

【问题讨论】:

    标签: javascript html web-component


    【解决方案1】:

    关于发布的代码有一些说明:

    CSS & shadowDOM

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

    这个 global CSS 永远不会应用于元素 shadowDOM 内的 HTML。

    请注意,您不必使用 shadowDOM。

    • 模板
    • 自定义元素 API
    • shadowDOM

    是 3 种不同的技术,您可以在没有彼此的情况下使用。

    要应用样式,请在 inside shadowDOM 中声明它:

    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>
    

    模板

    它只是一个字符串,你没有使用或声明&lt;template&gt;

    点击事件

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

    &lt;button&gt; 本身无需设置处理程序,您可以在任何父级上设置。

    垃圾回收

    无需删除自定义元素内部/之上的侦听器。 JavaScript 垃圾收集 过程将为您完成这项工作

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

    disabledCallback

    不存在;它叫disconnectedCallback()

    组成=真

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

    这只是因为事件不是从 shadowDOM 分派的。

    但是从 shadowDOM 内部调度,或使用 shadowDOM 将元素包装在另一个元素中,代码将不再工作,因为默认情况下自定义事件不会“转义”shadowDOM。

    为此,您需要使用:

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

    注意:defaultclick 这样的事件 do 默认转义 shadowDOM

    解决方案

    如果没有 shadowDOM,唯一需要的代码是:

    • 全局 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>

    使用 shadowDOM

    • 对 CSS 类的需求较少,因为所有 HTML 都隔离在 shadowDOM 中

    • &lt;template&gt; 可以声明为 HTML(这样您的 IDE 可以整齐地格式化里面的所有内容)被 this.nodeName 引用,因此您可以重复使用代码

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

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

    • 这些只是 Web 组件的技术增强,可能会加载更多的功能增强,这一切都取决于用例

    • 在继续阅读 &lt;slot&gt; 内容之前,请阅读 ::slotted CSS selector for nested children in shadowDOM 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>

    【讨论】:

      【解决方案2】:

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

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

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

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

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

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

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

      你可以查看here这个的使用方法。

      此外,父母将使用:

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

      所以,当按钮被点击时,父级会触发函数changeColor,里面有你想要的逻辑。

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

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

      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>

      【讨论】:

      • 所以我明白,由于您的回答和您放在这里的链接之一,我最初的想法是这种 Web 组件架构的不好做法?
      • 是的,最好的方法是不要直接在组件/父级之间进行对话,因为这是一个耦合应用程序。与您想要使用 Web 组件的情况正好相反。
      猜你喜欢
      • 1970-01-01
      • 2017-06-05
      • 2012-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-13
      相关资源
      最近更新 更多