【问题标题】:Access class function in Web Component from inline Element从内联元素访问 Web 组件中的类函数
【发布时间】:2019-05-26 13:51:21
【问题描述】:

我想从我的 Web 组件中的元素执行定义的类函数:

customElements.define('first-component', class FirstComponent extends HTMLElement {
    constructor() {
        super();
     }

     log() {
        console.log('Well Done!')
     }

     connectedCallback() {
        this.innerHTML = '<button onclick="log()">Do it</button>'
     }
});

当前状态:ReferenceError: log is not defined

【问题讨论】:

    标签: javascript web-component custom-element


    【解决方案1】:

    这是我使用模板文字和窗口属性的方法。

    通过使用模板文字,您可以获得所需的范围,但 onclick 事件仍然需要访问您的方法。我想在这种情况下,使用window 是从外部范围共享事件的唯一方法。由于事件采用字符串类型,我可以让它调用window 和共享方法所在的一些唯一属性名称。

    customElements.define('first-component', class FirstComponent extends HTMLElement {
       log() {
          console.log('Well Done!')
       }
    
       bind(callback) {
         if (!callback) return '';
         const name = 'generate_random_name';
         window[name] = callback;
         return `window.${name}();`;
       }
    
       connectedCallback() {
          this.innerHTML = `<button onclick=${this.bind(() => this.log())}>Do it</button>`
       }
    });
    &lt;first-component&gt;&lt;/first-component&gt;

    更进一步,我们还可以通过使用标记模板文字来摆脱bind 方法。这允许我们在渲染之前操纵模板字符串及其动态值。将标签命名为“html”很有用,因为它启用了 html 的语法高亮显示。

    function html(strings, ...values) {
      let str = '';
      strings.forEach((string, index) => {
        const value = values[index];
        if (typeof value === 'function') {
          const callback = value;
          const name = 'generate_random_name';
          window[name] = callback;
          str += string + `window.${name}();`;
        } else {
          str += string
        }
      });
      return str;
    }
    
    customElements.define('first-component', class FirstComponent extends HTMLElement {
      log() {
        console.log('Well Done!')
      }
    
      connectedCallback() {
        this.innerHTML = html`<button onclick=${() => this.log()}>Do it</button>`
      }
    });
    &lt;first-component&gt;&lt;/first-component&gt;

    【讨论】:

      【解决方案2】:

      使用 parentElement,或最接近()

      为了调用自定义元素的log() 方法,您必须获取它的引用。

      在您的示例中,自定义元素是 &lt;button&gt; 元素的父元素,因此您应该调用按钮的 parentElement 属性,如 @Smankusors 所述:

      <button onclick="this.parentElement.log()>Do it</button>
      

      使用 getRootNode()

      或者,在更复杂的 DOM 树中,如果使用 Shadow DOM,您可以使用 getRootNode() 结合 host 来获取自定义元素引用。

      customElements.define('first-component', class FirstComponent extends HTMLElement {
           log() {
              console.log('Well Done!')
           }
      
           connectedCallback() {
              this.attachShadow({mode: 'open'})
                  .innerHTML = '<button onclick="this.getRootNode().host.log()">Do it</button>'
           }
      })
      &lt;first-component&gt;&lt;/first-component&gt;

      具有唯一标识符

      您还可以通过其id 属性(如果有的话)调用自定义元素:

      customElements.define('first-component', class FirstComponent extends HTMLElement {
           log() {
              console.log('Well Done!')
           }
      
           connectedCallback() {
              if (!this.id)
                  this.id = "_id"
              this.innerHTML = `<button onclick="${this.id}.log()">Do it</button>`
           }
      })
      &lt;first-component&gt;&lt;/first-component&gt;

      使用handleEvent()

      出于安全原因,您可以避免内联脚本并实现handleEvent() 方法,然后根据某些标准在其中调用特定方法:

      customElements.define('first-component', class FirstComponent extends HTMLElement {
          log() {
              console.log('Well Done!')
          }
           
          handleEvent(ev) {
              if (ev.target.innerText == 'Do it')
                  this.log()
          }
      
          connectedCallback() {
              this.innerHTML = '<button>Do it</button>'
              this.addEventListener('click', this)
          }
      })
      &lt;first-component&gt;&lt;/first-component&gt;

      【讨论】:

      • 您能否详细说明安全原因是什么?
      • @user2939415 搜索“javascript injection”或阅读例如glebbahmutov.com/blog/disable-inline-javascript-for-security
      • 对于不使用 Shadow DOM 的 Web 组件,是否有类似于 getRootNode() 的方法?
      • @user2939415 是:"this.closest('first-component').log()" 应该可以解决问题
      【解决方案3】:

      那不应该是log(),而是this.log(),因为log函数范围只是那个元素,不在window范围内,所以你的代码应该是

      customElements.define('first-component', class FirstComponent extends HTMLElement {
          constructor() {
              super();
           }
      
           log() {
              console.log('Well Done!')
           }
      
           connectedCallback()
              this.innerHTML = '<button onclick="this.parentElement.log()">Do it</button>'
           }
      });
      

      -- 编辑-- 对不起,我的错误,我刚刚看到你在自定义元素中添加了按钮,嗯......如果你仍然想要内联,应该是this.parentElement.log()

      【讨论】:

      • 之前也试过。问题是这将绑定到按钮并且也会失败。
      • 你试过这个吗?这个不行,this这里还是窗口作用域
      • 哎呀,对不起,你可以试试this.parentElement.log()
      【解决方案4】:

      出于多种原因,您应该停止使用内联事件侦听器。相反,使用addEventListener - 在这种情况下使用connectedCallback

      customElements.define('first-element', class FirstElement extends HTMLElement {
          constructor() {
              super();
           }
      
           log() {
              console.log('Well Done!')
           }
      
           connectedCallback() {
              const btn = document.createElement('button');
              btn.textContent = 'Do it!';
              btn.type = 'button'; // otherwise it's type=submit
              btn.addEventListener('click', this.log);
              this.appendChild(btn);
           }
      });
      &lt;first-element&gt;&lt;/first-element&gt;

      【讨论】:

      • 为什么使用内联事件监听器不好?我认为它更具可读性,许多框架都遵循这种模式
      • 那些框架解析 HTML 并在内部将内联事件侦听器替换为 addEventListener。使用本机 Web 组件,您将处于不同的环境中。内联事件侦听器 - 除了因为内联样式不好的原因而变得糟糕之外 - 也不符合内容安全策略,该策略不能应用于使用内联事件侦听器的代码。
      【解决方案5】:

      由于 DOM 及其元素不知道它所在的范围,因此仅设置 innerHTML 的值是行不通的,因为 log 不存在于 window 上,这是 DOM 范围。因此,最佳实践是创建元素并将其附加到自定义元素的 Shadow Dom,同时将 eventListener 添加到按钮。

      customElements.define('first-component', class FirstComponent extends HTMLElement {
          constructor() {
              super();
           }
      
           log() {
              console.log('Well Done!')
           }
      
           connectedCallback() { // This parentheses was also missing
               var shadow = this.attachShadow({mode: 'open'});
               const button = document.createElement("button");
               button.textContent = 'Do it!';
               button.addEventListener('click', () => this.log());
               shadow.appendChild(button);
           }
      });
      &lt;first-component id="component"&gt;&lt;/first-component&gt;

      【讨论】:

      • 它有效,但我不喜欢这个解决方案,哈哈!谢谢
      • 不确定是否还有其他解决方案 tbh @Fabian 。我想你可以看看stackoverflow.com/questions/37866237/… 看看是否有帮助,我知道它指向反应,但它或多或少是原生的
      猜你喜欢
      • 2017-06-23
      • 2011-10-14
      • 2014-07-13
      • 2017-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-08
      • 2023-01-17
      相关资源
      最近更新 更多