首页 > 解决方案 > 如果通过 html 属性订阅,为什么我的 Web 组件的 CustomEvent 事件处理程序没有被调用?

问题描述

Windows 10 x64,谷歌浏览器 v87.0.4280.66。

如果我通过 JavaScript 订阅组件的事件,那么我的事件处理程序可以正常工作。但是,如果我尝试通过 HTML 文件中的属性来做同样的事情,那么我的事件处理程序将不起作用。

所以,它工作正常:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Custom component</title>
</head>
<body>
<custom-counter id="counter" counter="5"></custom-counter>
<script src="custom-counter.js"></script>
</body>
</html>

代码:

// custom-counter.js

class CustomCounter extends HTMLElement {

    static get htmlTagName() { return "custom-counter"; }

    constructor() {
        super();
    }

    #shadow;

    #render() {
        console.log("#render called");
        if(this.#shadow) {
            this.#shadow.getElementById("counter").textContent = `* ${this.counter.toString()} *`;
        }
    }

    get counter() {
        const attName = "counter";
        return this.hasAttribute(attName) ? parseInt(this.getAttribute(attName)) : 0;
    }

    set counter(value) {
        const attName = "counter";
        this.setAttribute(attName, value);
        this.dispatchEvent(new CustomEvent("counterchanged", {detail: { value: this.counter}}));
    }

    connectedCallback() {
        this.#shadow = this.attachShadow({mode:"closed"});
        this.#shadow.innerHTML = `<div><span id="counter">* ${this.counter} *</span></div>`;
        const element = this.#shadow.getElementById("counter");
        element.style.background = "yellow";
        element.addEventListener("click", event => ++this.counter);
    }

    attributeChangedCallback(attName, oldValue, newValue) {
        console.log(`The '${attName}' attribute changed. oldValue: ${oldValue}. newValue: ${newValue}`);
        this.#render();
    }

    static get observedAttributes() {
        return ["counter", "counterchanged"];
    }
}

customElements.whenDefined(CustomCounter.htmlTagName).then(
    () => console.log(`'${CustomCounter.htmlTagName}' web component is defined.`));

if(!customElements.get(CustomCounter.htmlTagName)) {
    customElements.define(CustomCounter.htmlTagName, CustomCounter);
}

// It works fine:
const counter = document.getElementById("counter");
counter.addEventListener("counterchanged", event => console.log(event.detail));

看起来像这样:

在此处输入图像描述

控制台输出(当我单击我的 Web 组件时):

在此处输入图像描述

好的。现在我想将事件注册从代码移动到 html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Custom component</title>
</head>
<body>
<custom-counter id="counter" counter="5" oncounterchanged="console.log(event.detail)"></custom-counter>
<script src="custom-counter.js"></script>
</body>
</html>

代码:

// custom-counter.js

class CustomCounter extends HTMLElement {

    static get htmlTagName() { return "custom-counter"; }

    constructor() {
        super();
    }

    #shadow;

    #render() {
        console.log("#render called");
        if(this.#shadow) {
            this.#shadow.getElementById("counter").textContent = `* ${this.counter.toString()} *`;
        }
    }

    get counter() {
        const attName = "counter";
        return this.hasAttribute(attName) ? parseInt(this.getAttribute(attName)) : 0;
    }

    set counter(value) {
        const attName = "counter";
        this.setAttribute(attName, value);
        this.dispatchEvent(new CustomEvent("counterchanged", {detail: { value: this.counter}}));
    }

    connectedCallback() {
        this.#shadow = this.attachShadow({mode:"closed"});
        this.#shadow.innerHTML = `<div><span id="counter">* ${this.counter} *</span></div>`;
        const element = this.#shadow.getElementById("counter");
        element.style.background = "yellow";
        element.addEventListener("click", event => ++this.counter);
    }

    attributeChangedCallback(attName, oldValue, newValue) {
        console.log(`The '${attName}' attribute changed. oldValue: ${oldValue}. newValue: ${newValue}`);
        this.#render();
    }

    static get observedAttributes() {
        return ["counter", "counterchanged"];
    }
}

customElements.whenDefined(CustomCounter.htmlTagName).then(
    () => console.log(`'${CustomCounter.htmlTagName}' web component is defined.`));

if(!customElements.get(CustomCounter.htmlTagName)) {
    customElements.define(CustomCounter.htmlTagName, CustomCounter);
}

// It works fine:
/* const counter = document.getElementById("counter");
counter.addEventListener("counterchanged", event => console.log(event.detail));
*/

但现在我的事件处理程序不起作用:

在此处输入图像描述

为什么会发生,我该如何解决?

标签: javascriptweb-component

解决方案


我自己解决了这个问题(查看connectedCallback函数中的注释):

// custom-counter.js

class CustomCounter extends HTMLElement {

    static get htmlTagName() { return "custom-counter"; }

    constructor() {
        super();
    }

    #shadow;

    #render() {
        console.log("#render called");
        if(this.#shadow) {
            this.#shadow.getElementById("counter").textContent = `* ${this.counter.toString()} *`;
        }
    }

    get counter() {
        const attName = "counter";
        return this.hasAttribute(attName) ? parseInt(this.getAttribute(attName)) : 0;
    }

    set counter(value) {
        const attName = "counter";
        this.setAttribute(attName, value);
        this.dispatchEvent(new CustomEvent("counterchanged", {detail: { value: this.counter}}));
    }

    connectedCallback() {
        this.#shadow = this.attachShadow({mode:"closed"});
        this.#shadow.innerHTML = `<div><span id="counter">* ${this.counter} *</span></div>`;
        const element = this.#shadow.getElementById("counter");
        element.style.background = "yellow";
        element.addEventListener("click", event => ++this.counter);
        
        // I added this code block:
        if(this.hasAttribute("oncounterchanged")) {
            const code = this.getAttribute("oncounterchanged");
            if(code){
                this.addEventListener("counterchanged", new Function(code));
            }
        }
    }

    attributeChangedCallback(attName, oldValue, newValue) {
        console.log(`The '${attName}' attribute changed. oldValue: ${oldValue}. newValue: ${newValue}`);
        this.#render();
    }

    static get observedAttributes() {
        return ["counter"];
    }
}

customElements.whenDefined(CustomCounter.htmlTagName).then(
    () => console.log(`'${CustomCounter.htmlTagName}' web component is defined.`));

if(!customElements.get(CustomCounter.htmlTagName)) {
    customElements.define(CustomCounter.htmlTagName, CustomCounter);
}

// It works fine:
/* const counter = document.getElementById("counter");
counter.addEventListener("counterchanged", event => console.log(event.detail));
*/

推荐阅读