Web Components #5 - 组件生命周期
组件的生命周期
- constructor: 组件初始化时,被调用
- connectedCallback: 当 组件 首次被插入文档 DOM 时,被调用。
- disconnectedCallback:当 组件 从文档 DOM 中删除时,被调用。
- adoptedCallback:当 组件 被移动到新的文档时,被调用。
- attributeChangedCallback: 当 组件 增加、删除、修改自身属性时,被调用。
例有如下组件:
class MyCard extends HTMLElement {
static get observedAttributes() {
return ["name", "size"];
}
constructor() {
super();
console.log('constructor');
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML=`
<div style="border:1px solid gray;">
<p><slot>组件</slot></p>
</div>
`
}
connectedCallback() {
console.log('connectedCallback');
}
disconnectedCallback() {
console.log('disconnectedCallback');
}
adoptedCallback() {
console.log('adoptedCallback');
}
attributeChangedCallback(name, oldValue, newValue) {
console.log('attributeChangedCallback');
}
}
customElements.define('my-card', MyCard);
constructor ,实例化
- 使用html方式插入组件
<my-card >组件1</my-card>
<my-card >组件2</my-card>
控制面板显示:
constructor
connectedCallback
constructor
connectedCallback
说明:
组件被实例化了两次.
- 在JS使用组件
// constructor触发
const myCard=document.createElement('my-card');
直接new
window.card=new MyCard();// construct
connectedCallback
插入到dom时才发生.
<my-card >组件1</my-card>
或者在appendChild
时发生.
插入到DocumentFragment
不触发.
constructor
和connectedCallback
有什么区别.
constructor
是对象创建,相当于new时发生,dom往往没准备好.
connectedCallback
需要有dom操作,获取dom内容和属性时.
window.card=new MyCard();// construct
document.body.appendChild(window.card);// connectedCallback
disconnectedCallback,组件被删除
删除,移动时触发事件
document.querySelector("my-card").remove();
移动到dom
document.body.appendChild(document.querySelector("my-card"));
移动到DocumentFragment
const div=document.createDocumentFragment("div");
div.appendChild(document.querySelector("my-card"));
注意,对象被删除引用时并不能触发disconnectedCallback.如delete window.card
card=null
等这种情况.
window.card=new MyCard();// construct
document.body.appendChild(window.card);// connected
window.card=null;// 不触发
delete window.card;// 不触发
adoptedCallback
移动到新页面.
"新页面或新文档":指 window.open()
,document.implementation.createHTMLDocument
,DOMParser的parseFromString
,iframe
等
- window.open:
// 新页面
const newPage=window.open('', '_blank');
newPage.document.body.appendChild(document.querySelector("my-card"));
触发三个事件:
disconnectedCallback
adoptedCallback
connectedCallback
- createHTMLDocument:
console.log("移动到新页面")
const newDoc = document.implementation.createHTMLDocument('New Document');
newDoc.body.innerHTML=`new Doc`
newDoc.body.appendChild(document.querySelector("my-card"));
console.log("又移回来了")
// document.documentElement.replaceWith(newDoc.documentElement);
// document.body=newDoc.body;
document.querySelector("#app").replaceWith(newDoc.body);
- DOMParser:
const parser = new DOMParser();
const htmlString = '<html><body><h1>Hello, New Document</h1></body></html>';
const newDoc = parser.parseFromString(htmlString, 'text/html');
newDoc.body.appendChild(document.querySelector("my-card"));
document.documentElement.replaceWith(newDoc.body);
4.iframe:
const iframe=document.querySelector("iframe");
iframe.contentWindow.document.body.appendChild(document.querySelector("my-card"));
attributeChangedCallback
监听属性变化,注意需要定义监听哪些属性,不然无法监听
// 比如监听name,size的值
static get observedAttributes() {
return ["name", "size"];
}
/**
*
* @param {*} name 属性名
* @param {*} oldValue 原值
* @param {*} newValue 新值
*/
attributeChangedCallback(name, oldValue, newValue) {
console.log('attributeChangedCallback',name, oldValue, newValue);
}
触发
const card=document.querySelector("my-card");
card.setAttribute("size","100");
card.setAttribute("size","200");
card.removeAttribute("size");
注意: 标准属性,可以直接赋值,web组件也是JS对象.如id,class,title,value等,受到setter,getter影响.
注: 直接<my-card id="new-card"></my-card>
定义属性,并不触发setter.
js调用方式
const card=new MyCard(); // 1. 直接创建
const card=document.createElement("my-card");// 2. 通过customElements.define 注册来创建
const card=document.querySelector("my-card");// 3. 已定义的元素
监听属性的方式,
- setter/getter
class MyCard extends HTMLElement {
....
set id(newName) {
console.log('set id', newName);
// 重新设置属性,以触发attributeChangedCallback
this.setAttribute('id', newName);
}
get id() {
console.log('get id');
return this.getAttribute('id');
}
}
调用:
<my-card id="card"></my-card> // 不触发
<script>
card.id="new-card"; // 触发
card.setAttribute("id","card2"); // 不触发
</script>
- 重写setAttribute
class MyCard extends HTMLElement {
....
setAttribute(name, value) {
console.log('setAttribute',name,value);
super.setAttribute(name, value);
}
}
调用:
<my-card id="card"></my-card> // 不触发
<script>
card.id="new-card"; // 不触发
card.setAttribute("id","card2"); //触发
</script>
- 通过
MutationObserver
监听
class MyCard extends HTMLElement {
constructor() {
super();
....
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes') {
console.log('MutationObserver',mutation.target.getAttribute('id'));
}
}
});
observer.observe(this, { attributes: true });
}
结果 :
<my-card id="card"></my-card> // 不触发
<script>
// 2次结果都是card2
card.id="new-card";
card.setAttribute("id","card2");
requestAnimationFrame(function(){
card.id="new-card3";
})
</script>
- 通过Proxy
由于document.createElement() 必须是HTMLElement对象,不能直接Proxy方式.
- 通过Object.defineProperty
class MyCard extends HTMLElement {
constructor() {
super();
....
Object.defineProperty(this, 'id', {
set(value) {
console.log('set', value);
this.setAttribute('id', value);
},
get(){
console.log('get');
return this.getAttribute('id');
}
});
}
}
与getter/setter类似,不能监听setAttribute
结论: MutationObserver
和 attributeChangedCallback
能监听直接赋值和setAttribute.
但是MutationObserver
在moudle模式下不能完整监听.
web组件的属性变化,还是通过attributeChangedCallback
监听比较安全.
总结
web组件的生命周期,顺序是
constructor
-->attributeChangedCallback
-->connectedCallback
判断组件是否定义.
customElements.whenDefined("my-card").then(function(){
console.log("my-card",'Defined');
})
原作者:阿金
本文地址:https://hi-arkin.com/archives/web-components-5-1.html