Web Components,当 VueJS 过于复杂时的理想选择。
如今,如果你想做一个网站,就无法避免“VueJS”或“ReactJS”这两个词,这是有充分理由的。这些库基于组件的架构,以及它们处理数据/属性并相应地更新网站相关部分的方式,使得网站开发变得更加容易,简直就像魔法一样! ✨
但是,当我需要一个简单的组件,或者我想要的元素没有动态数据时,我会问自己“我真的需要 React/Vue 吗?🤔”,这时 Web 组件就派上用场了。
Web 组件是一些功能(不是元素本身),可以帮助你完成很多事情,其中之一就是创建一个自定义元素,它可以像input其他div元素一样使用。
开始吧!
步骤 1:定义我们的组件
实现此目的的一种方法是创建一个实现该HTMLElement接口的类,并使用该函数为其指定一个标签名称customElements.define。
HTMLElement 接口代表任何 HTML 元素。有些元素直接实现此接口,而另一些元素则通过继承此接口来实现。
//component.js
class MyComponent extends HTMLElement {
constructor(){
super();
console.log("My component works!");
}
}
customElements.define("my-component", MyComponent); //Register the new element
请注意,组件名称使用了连字符,这是因为我们不允许创建类似 `<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>
如果我们查看控制台,就会看到这条消息"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);
现在我们在 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>
现在当我们按下按钮时,我们的组件被移除,我们"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)
让我解释一下这里发生了什么。
-
我们已将该元素重命名
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>
第四步:属性
我们的时钟目前看起来不错,但还可以做得更好。现在,我们将根据我们选择的属性,让它显示 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")
}
如果你仔细查看新增的代码,getTimeString你会看到一条非常奇怪的语句typeof military == "string",这是因为当我们像这样设置属性时:
<x-clock military></x-clock>
我们获取到的属性值""在 JavaScript 中等同于 false,因此if(military)即使该属性存在,也会返回 false。
现在我们可以通过添加属性来选择以 12 小时制或 24 小时制显示时间!
<!-- index.html -->
<body>
<h1>Hello world!</h1>
<x-clock></x-clock>
<x-clock military></x-clock>
</body>
步骤 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
});
}
我们将观察者分配给this.observer而不是,const observer因为我们需要清理我们中的监听器disconnectedCallback。
disconnectedCallback(){
//Disconnect observer
this.observer.disconnect();
//Clear the timer interval
clearInterval(this.intervalID);
console.log("Unmounted")
}
当属性发生变化时,我们需要显示准确的时间格式,为此,我们也需要进行更改const military,this.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");
}
我们完成了🎉🎉🎉🎉🎉🎉🎉🎉
我们不仅创建了自定义元素,还让它能够动态响应变化。这仅仅是 Web 组件功能的冰山一角,我迫不及待地想看看你们会用它创造出哪些精彩的作品。
再次强调,这并非 VueJS(或其同类框架)的替代品,而只是在Vue 过于强大时的一种替代方案。