Web Components 基础入门-生命周期(中)

2,690 阅读3分钟

中篇

Web Components 生命周期

关于自定义元素的 生命周期

♻️ 组件的生命周期

让我们看一下自定义元素的生命周期,下面是一份代码备注,方便开始快速开发。

class MyElementLifecycle extends HTMLElement {
  // 元素初始化的时候执行
  constructor() {
    // HTMLElement.prototype.constructor.call(this)
    super()
    console.log('constructed!')
  }
  /**
   * connectedCallback
   * 当元素插入到 DOM 中时,将调用 connectedCallback。
   * 这是运行安装代码的好地方,比如获取数据或设置默认属性。
   * 可以将其与React的componentDidMount方法进行比较
   * vue的mount方法作比较
   */
  connectedCallback() {
    console.log('connected!')
  }
  /**
   * disconnectedCallback
   * 只要从 DOM 中移除元素,就会调用 disconnectedCallback。清理时间到了!
   * 我们可以使用 disconnectedCallback 删除事件监听,或取消记时。
   * 但是请记住,当用户直接关闭浏览器或浏览器标签时,这个方法将不会被调用。
   *
   * 可以用window.unload beforeunload或者widow.close 去触发在浏览器关闭是的回调
   *
   * 可以与 react 中的 componentWillUnmount 的方法进行比较
   * vue 中的 destory中是生命周期函数进行对比
   */
  disconnectedCallback() {
    console.log('disconnected!')
  }

  static get observedAttributes() {
    return ['my-attr']
  }
  /**
   *
   * @param {*} name
   * @param {*} oldVal
   * @param {*} newVal
   *
   * 每当添加到observedAttributes数组的属性发生变化时,就会调用这个函数。使用属性的名称、旧值和新值调用该方法
   * react 中的 static getDerivedStateFromProps(props, state) 有些类似
   * 基本上和vue中的watch使用和observedAttributes + attributeChangedCallback使用雷同;
   */

  attributeChangedCallback(name, oldVal, newVal) {
    console.log(`Attribute: ${name} changed!`)
  }
  /**
   * 每次将自定义元素移动到新文档时,都会调用 adoptedCallback。只有当您的页面中有 < iframe > 元素时,您才会遇到这个用例。
   * 通过调用document.adoptnode (element)调用它,基本上用不上
   */
  adoptedCallback() {
    console.log('adopted!')
  }
  /**
   * 生命周期的执行顺序  挂载的时候 按照react 或者vue中的执行顺序是相同的
   * constructor -> attributeChangedCallback -> connectedCallback
   */
}
// 不是生命周期的API 但是非常重要 注册
window.customElements.define('my-element-lifecycle', MyElementLifecycle)

看过上面的的代码注释后,下面关于生命周期的介绍是重复的;

constructor() 初始化

constructor()方法是元素挂载之前运行的,一般会在这个函数中设置一些元素的初始状态,还有事件的监听 以及创建 Shadow Dom

类比 Vue 中的 create()Reactclass 组件中的 constructor()它们两个是一样的

connectedCallback() 挂载完成

当元素插入到 DOM 中时,将调用 connectedCallback, 通常,组件的一些样式或者其他需要获取元素的状态 应该在 connectedCallback 进行设置,因为在这里才能确定元素的所有属性和子元素都是可用的。constructor 一般应该只用于初始化状态和设置shadow DOM

constructorconnectedCallback 之间的区别

创建元素时调用constructor, 已经插入到 DOM 元素后调用 connectedCallback

可以将其与vuemount方法、ReactcomponentDidMount方法进行比较

disconnectedCallback 卸载元素

自定义元素DOM中移除的时候,就会触发该方法;同时也会在这个事件中删除调改元素添加的事件,比如全局的键盘函数以及延时函数, disconnectedCallback 的对应函数是 connectedCallback, 另外有个注意点,当用户关闭浏览器或关闭该标签页时,将不会调用此方法。不过可以用 window.unload beforeunload 或者 widow.close 去触发在浏览器关闭是的回调

react 中的 componentWillUnmount 的方法进行比较, vue 中的 destory 中是生命周期函数进行对比

attributeChangedCallback 属性监听

使用这个函数需要和 observedAttributes 该方法配合使用,需要将你要监听的属性在该方法中注册;

static get observedAttributes() {
    return ['my-attr']
}

上面的函数中,就是对my-attr属性进行了监听;当该属性的值发生改变的时候,就会触发attributeChangedCallback

/**
   *
   * @param {*} name 属性名称
   * @param {*} oldVal 改变前的值
   * @param {*} newVal 改变后的值
   *
   */
attributeChangedCallback(name, oldVal, newVal) {
    console.log(`Attribute: ${name} changed!`)
}

**注意:**只有在 observedAttributes 监听的值才会触发 attributeChangedCallback

react 中的 static getDerivedStateFromProps(props, state) 与这个有些类似,以及 vue 中的 watch 函数

执行顺序

constructor -> attributeChangedCallback -> connectedCallback;vue 以及 react 中的生命周期也是这样的一个顺序;

这个时候,可能会有些困惑了。

为什么 attributeChangedCallback 要在 connectedCallback 之前执行 ?

回想一下,web 组件上的属性主要用来初始化配置。这意味着当组件被插入DOM时,这些配置需要可以被访问了。因此attributeChangedCallback要在connectedCallback之前执行。 这意味着你需要根据某些属性的值,在Shadow DOM中配置任何节点,那么你需要在构造函数中引用这些节点,而不是在connectedCallback中引用它们。

例如,如果你有一个IDcontainer的组件,并且你需要在根据属性的改变来决定是否给这个元素添加一个灰色的背景,那么你可以在构造函数中引用这个元素,以便它可以在attributeChangedCallback中使用:

constructor() {
  this.container = this.shadowRoot.querySelector('#container');
}
attributeChangedCallback(attr, oldVal, newVal) {
  if(attr === 'disabled') {
    if(this.hasAttribute('disabled') {
      this.container.style.background = '#808080';
    }
    else {
      this.container.style.background = '#ffffff';
    }
  }
}


如果你一直等到connectedCallback再去创建this.container。然后在第一时间调用attributeChangedCallback,它还是不可用的。因此尽管你应该尽可能的延后你组件的connectedCallback,但在这种情况下是不可能的。

customels.define

另外,在自定义元素未注册时候使用它,它会仅仅作为 HTMLUnknownElement,一个实例;代表着一个无效的 HTML 元素,派生自 HTMLElement 接口,,但它没有任何可用的附加属性或方法

然后通过 customements.define() 注册时,它会继承HTMLElement这个类。这个过程称为升级。 通过 customElements.whenDefined方法,当自定义元素被定义时一个 Promise 返回{jsxref("undefined")}}. 如果自定义元素已经被定义,则 resolve 立即执行


Promise<> customElements.whenDefined(name);

customElements.whenDefined('my-element').then(() => {
  // my-element is now defined
})

浏览器的支持情况

Edge 在发布的 19 版本中开始提供支持。对于旧的浏览器,可以使用 polyfill 来兼容 IE11

image