Web 组件的另一个我们尚未谈论过的方面是,每当 Web 组件被添加到页面或从页面中移除时,都会调用一个 JavaScript 函数。 这些生命周期回调可用于许多用途,包括使元素感知其上下文。
文章系列
- Web 组件比您想象的更简单
- 交互式 Web 组件比您想象的更简单
- 在 WordPress 中使用 Web 组件比您想象的更简单
- 使用 Web 组件增强内置元素“比”您想象的更简单
- 上下文感知的 Web 组件比您想象的更简单 (您就在这里!)
- Web 组件伪类和伪元素比您想象的更简单
Web 组件的四个生命周期回调
您可以使用 Web 组件 四个生命周期回调
connectedCallback
:当自定义元素附加到元素时,此回调会触发。disconnectedCallback
:当元素从文档中移除时,此回调会触发。adoptedCallback
:当元素添加到新文档时,此回调会触发。attributeChangedCallback
:当属性发生更改、添加或移除时,此回调会触发,前提是该属性正在被观察。
让我们看看这些回调在实际中的应用。
我们的末日人物组件
我们将从创建一个名为 <postapocalyptic-person>
的 Web 组件开始。 末日后的每个人都是人类或僵尸,我们根据应用于 <postapocalyptic-person>
组件父元素的类来识别他们的身份——无论是 .human
还是 .zombie
。 我们不会对其进行任何花哨的操作(至少现在不会),但我们将添加一个 shadowRoot
,我们可以使用它根据分类附加相应的图像。
customElements.define(
"postapocalyptic-person",
class extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "open" });
}
}
我们的 HTML 代码如下所示
<div class="humans">
<postapocalyptic-person></postapocalyptic-person>
</div>
<div class="zombies">
<postapocalyptic-person></postapocalyptic-person>
</div>
connectedCallback
插入人物
使用 当 <postapocalyptic-person>
加载到页面上时,会调用 connectedCallback()
函数。
connectedCallback() {
let image = document.createElement("img");
if (this.parentNode.classList.contains("humans")) {
image.src = "https://assets.codepen.io/1804713/lady.png";
this.shadowRoot.appendChild(image);
} else if (this.parentNode.classList.contains("zombies")) {
image.src = "https://assets.codepen.io/1804713/ladyz.png";
this.shadowRoot.appendChild(image);
}
}
这确保了当 <postapocalyptic-person>
是人类时,会输出人类的图像,当组件是僵尸时,会输出僵尸的图像。
使用 connectedCallback
时要小心。 它比您想象的运行频率更高,每当元素移动时都会触发,甚至可能在节点不再连接之后运行(令人困惑),这会导致性能成本很高。 您可以使用 this.isConnected
来判断元素是否已连接。
connectedCallback()
计数
在添加人物时使用 让我们稍微复杂一点,在其中添加几个按钮。 一个按钮将添加一个 <postapocalyptic-person>
,使用“抛硬币”的方法来决定它是人类还是僵尸。 另一个按钮将做相反的操作,随机移除一个 <postapocalyptic-person>
。 我们将在过程中跟踪有多少人类和僵尸在视野中。
<div class="btns">
<button id="addbtn">Add Person</button>
<button id="rmvbtn">Remove Person</button>
<span class="counts">
Humans: <span id="human-count">0</span>
Zombies: <span id="zombie-count">0</span>
</span>
</div>
以下是我们的按钮将执行的操作
let zombienest = document.querySelector(".zombies"),
humancamp = document.querySelector(".humans");
document.getElementById("addbtn").addEventListener("click", function () {
// Flips a "coin" and adds either a zombie or a human
if (Math.random() > 0.5) {
zombienest.appendChild(document.createElement("postapocalyptic-person"));
} else {
humancamp.appendChild(document.createElement("postapocalyptic-person"));
}
});
document.getElementById("rmvbtn").addEventListener("click", function () {
// Flips a "coin" and removes either a zombie or a human
// A console message is logged if no more are available to remove.
if (Math.random() > 0.5) {
if (zombienest.lastElementChild) {
zombienest.lastElementChild.remove();
} else {
console.log("No more zombies to remove");
}
} else {
if (humancamp.lastElementChild) {
humancamp.lastElementChild.remove();
} else {
console.log("No more humans to remove");
}
}
});
以下是 connectedCallback()
中的代码,用于在添加人物时计数人类和僵尸
connectedCallback() {
let image = document.createElement("img");
if (this.parentNode.classList.contains("humans")) {
image.src = "https://assets.codepen.io/1804713/lady.png";
this.shadowRoot.appendChild(image);
// Get the existing human count.
let humancount = document.getElementById("human-count");
// Increment it
humancount.innerHTML = parseInt(humancount.textContent) + 1;
} else if (this.parentNode.classList.contains("zombies")) {
image.src = "https://assets.codepen.io/1804713/ladyz.png";
this.shadowRoot.appendChild(image);
// Get the existing zombie count.
let zombiecount = document.getElementById("zombie-count");
// Increment it
zombiecount.innerHTML = parseInt(zombiecount.textContent) + 1;
}
}
disconnectedCallback
更新计数
使用 接下来,我们可以使用 disconnectedCallback()
在移除人类和僵尸时递减数量。 但是,我们无法检查父元素的类,因为在调用 disconnectedCallback
时,具有对应类的父元素已经消失了。 我们可以为元素设置一个属性,或为对象添加一个属性,但由于图像的 src
属性已经由其父元素确定,我们可以将其用作代理,以确定正在移除的 Web 组件是人类还是僵尸。
disconnectedCallback() {
let image = this.shadowRoot.querySelector('img');
// Test for the human image
if (image.src == "https://assets.codepen.io/1804713/lady.png") {
let humancount = document.getElementById("human-count");
humancount.innerHTML = parseInt(humancount.textContent) - 1; // Decrement count
// Test for the zombie image
} else if (image.src == "https://assets.codepen.io/1804713/ladyz.png") {
let zombiecount = document.getElementById("zombie-count");
zombiecount.innerHTML = parseInt(zombiecount.textContent) - 1; // Decrement count
}
}
当心小丑!
现在(我在这里说的是经验之谈,当然),除了成群结队的僵尸向你的位置逼近外,唯一更可怕的东西就是一个小丑——只要一个就够了! 所以,即使我们已经处理了可怕的末日僵尸,我们也要增加小丑进入场景的可能性,使恐怖加倍。 事实上,我们将以一种方式实现这一点,即屏幕上的任何人类或僵尸都可能潜藏着小丑!
我收回我之前说过的话:一只僵尸小丑比一群“正常”的小丑还要可怕。 假设,如果发现任何类型的小丑——无论是人类还是僵尸——我们将通过将它们发送到完全不同的文档——一个 <iframe>
监狱,将它们与人类和僵尸群体隔离开来。 (我听说“扮小丑”可能比僵尸感染更具传染性。)
当我们将一个可疑的小丑从当前文档移动到 <iframe>
时,它不会销毁并重新创建原始节点; 相反,它会采用并连接该节点,首先调用 adoptedCallback
,然后调用 connectedCallback
。
除了具有 .clowns
类的主体之外,我们不需要在 <iframe>
文档中添加任何内容。 只要这个文档位于主文档的 iframe 中——而不是单独查看——我们甚至不需要 <postapocalyptic-person>
实例化代码。 我们将为人类预留一个空间,为僵尸预留另一个空间,当然还有小丑的监狱…… 呃…… 的 <iframe>
…… 乐趣。
<div class="btns">
<button id="addbtn">Add Person</button>
<button id="jailbtn">Jail Potential Clown</button>
</div>
<div class="humans">
<postapocalyptic-person></postapocalyptic-person>
</div>
<div class="zombies">
<postapocalyptic-person></postapocalyptic-person>
</div>
<iframe class="clowniframeoffun” src="adoptedCallback-iframe.html">
</iframe>
我们的“添加人物”按钮与上一个示例中的工作原理相同:它抛硬币,随机插入人类或僵尸。 当我们点击“监禁潜在小丑”按钮时,会再抛一次硬币,将僵尸或人类送入 <iframe>
监狱。
document.getElementById("jailbtn").addEventListener("click", function () {
if (Math.random() > 0.5) {
let human = humancamp.querySelector('postapocalyptic-person');
if (human) {
clowncollege.contentDocument.querySelector('body').appendChild(document.adoptNode(human));
} else {
console.log("No more potential clowns at the human camp");
}
} else {
let zombie = zombienest.querySelector('postapocalyptic-person');
if (zombie) {
clowncollege.contentDocument.querySelector('body').appendChild(document.adoptNode(zombie));
} else {
console.log("No more potential clowns at the zombie nest");
}
}
});
adoptedCallback
揭示小丑
使用 在 adoptedCallback
中,我们将根据它们对应的图像来判断小丑是僵尸类型还是人类类型,然后相应地更改图像。 connectedCallback
将在那之后被调用,但我们没有任何需要它做的事情,而且它所做的事情不会干扰我们的更改。 因此,我们可以保持原样。
adoptedCallback() {
let image = this.shadowRoot.querySelector("img");
if (this.parentNode.dataset.type == "clowns") {
if (image.src.indexOf("lady.png") != -1) {
// Sometimes, the full URL path including the domain is saved in `image.src`.
// Using `indexOf` allows us to skip the unnecessary bits.
image.src = "ladyc.png";
this.shadowRoot.appendChild(image);
} else if (image.src.indexOf("ladyz.png") != -1) {
image.src = "ladyzc.png";
this.shadowRoot.appendChild(image);
}
}
}
attributeChangedCallback
检测隐藏的小丑
使用 最后,我们有 attributeChangedCallback
。 与其他三个生命周期回调不同,我们需要观察 Web 组件的属性,以便回调触发。 我们可以通过在自定义元素的类中添加一个 observedAttributes()
函数来实现这一点,并让该函数返回一个属性名称数组。
static get observedAttributes() {
return [“attribute-name”];
}
然后,如果该属性发生更改——包括添加或移除——attributeChangedCallback
会触发。
现在,你需要担心的是,你认识和爱的人(或者那些在变成僵尸之前你认识和爱的人)中,有些人可能偷偷地是伪装成人的小丑。我已经设置了一个小丑探测器,它会观察一群人类和僵尸,当你点击“揭示小丑”按钮时,探测器会(通过完全科学且完全可信的方式,**不是**基于随机数字选择索引)将 data-clown="true"
应用于组件。当应用了此属性时,attributeChangedCallback
会触发并更新组件的图像以揭示其小丑般的颜色。
我还应该注意到,attributeChangedCallback
接受三个参数
- 属性的名称
- 属性的先前值
- 属性的新值
此外,回调允许你根据属性的变化程度,或根据两种状态之间的转换来进行更改。
以下是我们的 attributeChangedCallback
代码
attributeChangedCallback(name, oldValue, newValue) {
let image = this.shadowRoot.querySelector("img");
// Ensures that `data-clown` was the attribute that changed,
// that its value is true, and that it had an image in its `shadowRoot`
if (name="data-clown" && this.dataset.clown && image) {
// Setting and updating the counts of humans, zombies,
// and clowns on the page
let clowncount = document.getElementById("clown-count"),
humancount = document.getElementById("human-count"),
zombiecount = document.getElementById("zombie-count");
if (image.src.indexOf("lady.png") != -1) {
image.src = "https://assets.codepen.io/1804713/ladyc.png";
this.shadowRoot.appendChild(image);
// Update counts
clowncount.innerHTML = parseInt(clowncount.textContent) + 1;
humancount.innerHTML = parseInt(humancount.textContent) - 1;
} else if (image.src.indexOf("ladyz.png") != -1) {
image.src = "https://assets.codepen.io/1804713/ladyzc.png";
this.shadowRoot.appendChild(image);
// Update counts
clowncount.innerHTML = parseInt(clowncount.textContent) + 1;
zombiecount.innerHTML = parseInt(zombiecount.textContent) - 1;
}
}
}
就是这样!我们不仅发现 Web 组件回调和创建上下文感知自定义元素比你想象的更容易,而且检测末日小丑,尽管可怕,但也比你想象的更容易。你可以使用这些 Web 组件回调函数来检测哪些狡猾的末日小丑?
你知道吗,在你扩展 HTMLElement 来创建自定义元素类之后,你可以扩展该类来创建一个第二个自定义类。像往常一样,你在第二个构造函数中调用 super()。但第二个中的 connectedCallback() 可以与第一个执行任何不同操作,所有其他自定义方法都是其原型的一部分,如预期的那样。
我不知道…疯狂地开始写下想法