发布于 2026-01-06 8 阅读
0

Web Components,当 VueJS 过于复杂时的理想选择。

Web Components,当 VueJS 过于复杂时的理想选择。

如今,如果你想做一个网站,就无法避免“VueJS”或“ReactJS”这两个词,这是有充分理由的。这些库基于组件的架构,以及它们处理数据/属性并相应地更新网站相关部分的方式,使得网站开发变得更加容易,简直就像魔法一样!

但是,当我需要一个简单的组件,或者我想要的元素没有动态数据时,我会问自己“我真的需要 React/Vue 吗?🤔”,这时 Web 组件就派上用场了。

Web 组件是一些功能(不是元素本身),可以帮助你完成很多事情,其中​​之一就是创建一个自定义元素,它可以像input其他div元素一样使用。

开始吧!

步骤 1:定义我们的组件

实现此目的的一种方法是创建一个实现该HTMLElement接口的类,并使用该函数为其指定一个标签名称customElements.define

MDN报道。

HTMLElement 接口代表任何 HTML 元素。有些元素直接实现此接口,而另一些元素则通过继承此接口来实现。

//component.js

class MyComponent extends HTMLElement {
    constructor(){
        super();
        console.log("My component works!");
    }
}

customElements.define("my-component", MyComponent); //Register the new element
Enter fullscreen mode Exit fullscreen mode

请注意,组件名称使用了连字符,这是因为我们不允许创建类似 `<component_name>` 这样的组件名称coolcomponent,名称必须类似于 `<component_name>`x-cool-component或 `<component_name>`。cool-component

现在,当我们component.js在 HTML 文件中包含它时,我们就可以使用我们刚刚创建的组件了。

<!-- index.html -->

<body>
    <h1>Hello world!</h1>
    <my-component></my-component>
</body>
Enter fullscreen mode Exit fullscreen mode

如果我们查看控制台,就会看到这条消息"My component works!",这意味着我们的组件运行正常。

步骤 2:元素生命周期

就像 Vue 一样,它也存在生命周期回调,即

  • connectedCallback这是在我们的元素渲染完成后立即调用的。

  • disconnectedCallback当元素即将被移除时,就会调用此函数。

//component.js

class MyComponent extends HTMLElement {
    constructor(){
        super();
        console.log("My component works!");
    }

    connectedCallback(){
        console.log("Mounted!")
    }

    disconnectedCallback(){
        console.log("Unmounted!")
    }
}

customElements.define("my-component", MyComponent);
Enter fullscreen mode Exit fullscreen mode

现在我们在 index.html 中添加一个按钮,该按钮会移除我们的元素,以便我们可以测试所有生命周期回调。

<!-- index.html -->

<body>
    <h1>Hello world!</h1>
    <my-component id="mycomponent"></my-component>
    <button onclick="document.getElementById('mycomponent').remove()">Remove Component</button>
</body>
Enter fullscreen mode Exit fullscreen mode

现在当我们按下按钮时,我们的组件被移除,我们"Unmounted!"在控制台中看到该消息。

步骤三:我们来做点什么吧。

现在我们已经掌握了创建自定义元素的基本知识,让我们来运用它吧!时钟元素就是一个很好的例子。

警告!!!!代码炸弹即将到来!!!! 💣💣💣

//component.js

class ClockElement extends HTMLElement {
    constructor(){
        super();
        // Time update interval id
        this.intervalID = 0;
    }

    pad(str){
        if(str.length == 1){
            str = "0"+str
        }
        return str;
    }

    //Check if hour is pm or am
    pmOrAm(hour){
        return Number(hour) < 12 ? "am" : "pm";
    }

    getTimeString(){
        const date = new Date();
        const seconds = date.getSeconds().toString()
        const hours = date.getHours().toString()
        const minutes = date.getMinutes().toString()

        var hoursNumber = Number(hours)
        var regularHOurs = hoursNumber-12<=0? hoursNumber : hoursNumber-12;
        return this.pad(regularHOurs.toString())+":"+this.pad(minutes)+":"+this.pad(seconds)+" "+this.pmOrAm(hours)
    }

    disconnectedCallback(){
        //Clear the timer interval
        clearInterval(this.intervalID);
        console.log("Unmounted")
    }

    connectedCallback(){
        //Start the timer
        this.intervalID = setInterval(()=>{
            this.innerHTML = this.getTimeString()
        },1000);
        console.log("Mounted")
    }
}

customElements.define("x-clock",ClockElement)
Enter fullscreen mode Exit fullscreen mode

让我解释一下这里发生了什么。

  • 我们已将该元素重命名ClockElement并注册为x-clock

  • 现在使用一个间隔 ID 来识别并最终清除在 [此处应填写具体内容] 中声明的间隔。connectedCallback

  • pad方法用于在个位数后面加 0,这样时间看起来就像00:09:16原本应该的样子。0:9:16

  • pmOrAm方法根据小时返回相应的时间后缀。

  • 这种getTimeString方法看起来很复杂,但实际上并非如此,我们只需要获取当前的小时、分钟和秒,然后将其转换为一个以 12 小时制格式显示的字符串即可。

  • 在代码中connectedCallback,我们启动一个计时器,每隔 1000 毫秒(1 秒)将元素的 innerHTML 设置为当前时间。

  • 我们disconnectedCallback清空计时器。

现在我们已经理解了这段代码,让我们把这个元素添加到我们的网站中。

<!-- index.html -->

<body>
    <h1>Hello world!</h1>
    <x-clock></x-clock>
</body>
Enter fullscreen mode Exit fullscreen mode

第四步:属性

我们的时钟目前看起来不错,但还可以做得更好。现在,我们将根据我们选择的属性,让它显示 24 小时制或 12 小时制格式。我个人比较喜欢这种语法:

<x-clock military></x-clock>

因此,我们将尝试把属性是否存在作为布尔值来使用。

    getTimeString(military){
        const date = new Date();
        const seconds = date.getSeconds().toString()
        const hours = date.getHours().toString()
        const minutes = date.getMinutes().toString()

        if(typeof military == "string"){
            return this.pad(hours)+":"+this.pad(minutes)+":"+this.pad(seconds)
        } else {
            var hoursNumber = Number(hours)
            var regularHOurs = hoursNumber-12<=0? hoursNumber : hoursNumber-12;
            return this.pad(regularHOurs.toString())+":"+this.pad(minutes)+":"+this.pad(seconds)+" "+this.pmOrAm(hours)
        }
    }

    disconnectedCallback(){
        //Clear the timer interval
        clearInterval(this.intervalID);
        console.log("Unmounted")
    }

    connectedCallback(){
        const military = this.getAttribute("military")
        this.innerHTML = this.getTimeString(military)
        this.intervalID = setInterval(()=>{
            this.innerHTML = this.getTimeString(military)
        },1000);
        console.log("Mounted")
    }
Enter fullscreen mode Exit fullscreen mode

如果你仔细查看新增的代码,getTimeString你会看到一条非常奇怪的语句typeof military == "string",这是因为当我们像这样设置属性时

<x-clock military></x-clock>
Enter fullscreen mode Exit fullscreen mode

我们获取到的属性值""在 JavaScript 中等同于 false,因此if(military)即使该属性存在,也会返回 false。

现在我们可以通过添加属性来选择以 12 小时制或 24 小时制显示时间!

<!-- index.html -->

<body>
    <h1>Hello world!</h1>
    <x-clock></x-clock>
    <x-clock military></x-clock>
</body>
Enter fullscreen mode Exit fullscreen mode

步骤 5:反应状态

目前,即使属性发生了变化,我们的元素在运行时状态也不会改变,这似乎还有改进的空间。因此,我们将使元素能够响应属性的变化。

为此,我们使用一个MutationObserver,它可以帮助我们监视元素的任何变化。

最好把这段代码放在元素构造函数里。MutationObserver构造函数会返回一个 MutationObserver 对象,当元素发生更改时,该对象会调用指定的回调函数。

    constructor(){
        super();
        // Time update interval id
        this.intervalID = 0;
        this.observer = new MutationObserver((mutations)=>{
            for(var mutation of mutations){
                if(mutation.type == "attribute"){
                    // React to changes
                }
            }
        });

        this.observer.observe(this,{
            attributes: true // Only listen for attribute changes
        });
    }
Enter fullscreen mode Exit fullscreen mode

我们将观察者分配给this.observer而不是,const observer因为我们需要清理我们中的监听器disconnectedCallback

    disconnectedCallback(){
        //Disconnect observer
        this.observer.disconnect();

        //Clear the timer interval
        clearInterval(this.intervalID);

        console.log("Unmounted")
    }
Enter fullscreen mode Exit fullscreen mode

当属性发生变化时,我们需要显示准确的时间格式,为此,我们也需要进行更改const militarythis.military以便我们可以从 MutationObserver 访问该变量。

    constructor(){
        super();
        // Time update interval id
        this.intervalID = 0;
        this.observer = new MutationObserver((mutations)=>{
            for(var mutation of mutations){
                if(mutation.type == "attribute"){
                    // React to changes
                    this.military = this.getAttribute("military");
                }
            }
        });

        this.observer.observe(this,{
            attributes: true // Only listen for attribute changes
        });
    }

    //Other code

    connectedCallback(){
        this.military = this.getAttribute("military")
        this.innerHTML = this.getTimeString(this.military);
        this.intervalID = setInterval(()=>{
            this.innerHTML = this.getTimeString(this.military);
        },1000);
        console.log("Mounted");
    }
Enter fullscreen mode Exit fullscreen mode

我们完成了🎉🎉🎉🎉🎉🎉🎉🎉

我们不仅创建了自定义元素,还让它能够动态响应变化。这仅仅是 Web 组件功能的冰山一角,我迫不及待地想看看你们会用它创造出哪些精彩的作品。

再次强调,这并非 VueJS(或其同类框架)的替代品,而只是在Vue 过于强大时的一种替代方案。

感谢阅读!

文章来源:https://dev.to/neutrino2211/web-components-for-when-vuejs-is-too-much-8em