组件的生命周期

  • 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 ,实例化

  1. 使用html方式插入组件
<my-card >组件1</my-card>
<my-card >组件2</my-card>

控制面板显示:

constructor
connectedCallback
constructor
connectedCallback

说明:

组件被实例化了两次.

  1. 在JS使用组件
// constructor触发
const myCard=document.createElement('my-card');

直接new

window.card=new MyCard();// construct

connectedCallback

插入到dom时才发生.
<my-card >组件1</my-card>

或者在appendChild 时发生.

插入到DocumentFragment 不触发.

constructorconnectedCallback有什么区别.

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

  1. window.open:
 // 新页面
const newPage=window.open('', '_blank');
newPage.document.body.appendChild(document.querySelector("my-card"));

触发三个事件:

disconnectedCallback
adoptedCallback
connectedCallback
  1. 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);
  1. 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. 已定义的元素

监听属性的方式,

  1. 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>
  1. 重写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>
  1. 通过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>
  1. 通过Proxy

由于document.createElement() 必须是HTMLElement对象,不能直接Proxy方式.

  1. 通过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

结论:
MutationObserverattributeChangedCallback 能监听直接赋值和setAttribute.

但是MutationObserver在moudle模式下不能完整监听.

web组件的属性变化,还是通过attributeChangedCallback监听比较安全.

总结

web组件的生命周期,顺序是

constructor-->attributeChangedCallback-->connectedCallback

image-20231021141955401

判断组件是否定义.

customElements.whenDefined("my-card").then(function(){
  console.log("my-card",'Defined');
})

原作者:阿金
本文地址:https://hi-arkin.com/archives/web-components-5-1.html

标签: web components 生命周期 web自定义组件

(本篇完)

评论