Web Components - Lifecycle & CSS

背景

在我们常用的 Vue、React 中,组件除了可以定义展示内容以外,还有一个很重要的点就是组件是有生命周期的,我们可以监听到组件的创建、销毁、属性变化,对于这一些我们 web components 是否也支持,答案是肯定的,今天我们来看看 web components 中的生命周期。

生命周期

在 web components 中,主要包含了以下 4 个回调函数,它们将会在元素的不同生命时期被调用:

  • connectedCallback:当 custom element首次被插入文档DOM时,被调用。
  • disconnectedCallback:当 custom element从文档DOM中删除时,被调用。
  • adoptedCallback:当 custom element被移动到新的文档时,被调用。
  • attributeChangedCallback: 当 custom element增加、删除、修改自身属性时,被调用。

加上 constructor,我们总共有 5 个生命周期。

接下来我们看下例子

<div id="app">
  <ol id="msg"></ol>

  <hello-world age="27">
    <span slot="username">hockor</span>
  </hello-world>

  <button id="remove">remove</button>
  <button id="change">change age</button>

  <template id="hello">
    <style>
      .hello {
        color: red;
      }
    </style>
    <div class="hello">
      hello,
      <slot name="username">world</slot>
      我的年龄是 <span id="age"></span>
    </div>
  </template>

</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
  function log (msg) {
    const li = document.createElement('span')
    li.innerHTML = msg + '<br/>'
    const msgPanel = document.getElementById('msg')
    msgPanel.appendChild(li)
  }

  class HelloWorld extends HTMLElement {
    constructor () {
      super()
      log('hello world 被构造了')
      const shadow = this.attachShadow({
        // closed 情况下通过this.shadowRoot拿到的是null
        mode: 'open'
      })
      const div = document.getElementById('hello')
      shadow.appendChild(div.content.cloneNode(true))
    }

    // 加入到DOM上
    connectedCallback () {
      log('hello world 被加入到DOM上了')
    }

    // 从DOM上移除了
    disconnectedCallback () {
      log('hello world 被删除了')
    }

    // attributesChangedCallback需要配合这个使用
    static get observedAttributes () {
      return ['age']
    }

    attributeChangedCallback (name, oldVal, newVal) {
      log('attributesChangedCallback')
      if ( name === 'age' ) {
        const age = this.shadowRoot.getElementById('age')
        age.innerHTML = newVal
      }
    }
  }

  customElements.define('hello-world', HelloWorld)


  $('#remove').click(function () {
    const hello = document.querySelector('hello-world')
    hello.parentNode.removeChild(hello)
  })

  $('#change').click(function () {
    const hello = document.querySelector('hello-world')
    hello.setAttribute('age', '34')
  })

</script>

demo地址:https://604n8h.csb.app/4.lifecycle.html

针对上面的 demo,做 1点说明

  • attributeChangedCallback 函数需要配合observedAttributes() get函数来实现,该函数返回一个数组,包含了需要监听的属性名,如果你没有加在这里的话,该属性发生变化的时候并不会触发attributeChangedCallback。

上面的 demo 中我们没有用到adoptedCallback,实际上这个回调在一般开发中是很少用到的。

adoptedCallback

adoptedCallback 本意是指的当 组件被移动到新文档时触发,注意这里的新文档,他一般是指的嵌套 iframe 场景下,我们来看个 demo

<div id="app">
  <ol id="msg"></ol>

  <hello-world age="27">
    <span slot="username">hockor</span>
  </hello-world>

  <button id="move">move</button>

  <iframe height="800" id="frame" src="./1.html" frameborder="0"></iframe>
  <template id="hello">
    <style>
      .hello {
        color: red;
      }
    </style>
    <div class="hello">
      hello,
      <slot name="username">world</slot>
      我的年龄是 <span id="age"></span>
    </div>
  </template>

</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
  function log (msg) {
    const li = document.createElement('span')
    li.innerHTML = msg + '<br/>'
    const msgPanel = document.getElementById('msg')
    msgPanel.appendChild(li)
  }

  class HelloWorld extends HTMLElement {
    constructor () {
      super()
      log('hello world 被构造了')
      const shadow = this.attachShadow({
        // closed 情况下通过this.shadowRoot拿到的是null
        mode: 'open'
      })
      const div = document.getElementById('hello')
      shadow.appendChild(div.content.cloneNode(true))
    }

    // 加入到DOM上
    connectedCallback () {
      log('hello world 被加入到DOM上了')
    }

    // iframe move
    adoptedCallback () {
      log('hello world 被移动了')
    }
  }

  customElements.define('hello-world', HelloWorld)

  // 按钮事件

  $('#move').click(function () {
    const hello = document.querySelector('hello-world')
    document.getElementById('frame').contentWindow.document.body.appendChild(hello)
  })

</script>

demo 地址:https://604n8h.csb.app/4.lifecycle2.html

在这个例子中,当我们将 component 从当前文档移动到 iframe 中的时候,adoptedCallback函数会被调用,而如果你是在当前文档移动 component 的话,会发现这个回调是不会触发的。

CSS

host

我们知道在 Antd / elementui 等框架中,当我们使用组件的时候可以传入一些内置的 class,比如 button 的“primary \ danger”,以此来使用 button 的不同样式,那么在 web components 中我们是不是也能这样呢?答案是可以的,我们可以通过 :host 来实现这个目的

老规矩,先上代码,再看 demo

<div id="app">
  <hello-world></hello-world>
  <hello-world class="blue small"></hello-world>
  <hello-world class="green large"></hello-world>

  <!--模板部分-->
  <template id="hello">
    <style>
      :host {
        display: block;
        color: red;
        border: 1px solid #333;
        width: 350px;
        height: 100px;
        line-height: 100px;
        margin: 30px;
        font-size: 16px;
      }

      /*可以通过括号对class做自定义,相当于你提供几套样式对外选择*/
      :host(.blue) {
        color: blue;
        font-size: 24px;
      }

      :host(.small) {
        font-size: 12px;
      }

      :host(.large) {
        font-size: 52px;
      }

      :host(.green) {
        color: green;
      }
    </style>
    <div class="hello">hello world</div>
  </template>

</div>
<script>
  class HelloWorld extends HTMLElement {
    constructor () {
      super()
      const shadow = this.attachShadow({
        mode: 'open'
      })
      const div = document.getElementById('hello')

      shadow.appendChild(div.content.cloneNode(true))
    }
  }
  customElements.define('hello-world', HelloWorld)
</script>

demo 地址:https://604n8h.csb.app/5.css-host.html

我们先看看 :host 官方说明:https://developer.mozilla.org/zh-CN/docs/Web/CSS/:host()

可以看到 :host 表示的就是我们 shadow dom 的宿主,同时我们可以把相关的类选择器作为函数参数包含在内,这样子就能做到内部自定义样式了。

part

同时,shadow dom 中还包含一个以元素中 part 属性名称组成的列表,该列表以空格分隔。通过 Part 的名称,可以使用 CSS 伪元素“::part”来选择 shadow 树中指定元素并设置其样式 。

part 属性官方说明:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/part

例子如下:

<style>
    p {
      font-size: 16px;
    }

    hello-world {
      margin: 20px
    }

    /*在外部通过part去自定义颜色*/
    hello-world::part(name-part) {
      color: orange;
    }

    hello-world::part(age-part) {
      color: #a65563;
    }
  </style>

<div id="app">
  <hello-world></hello-world>
  <hello-world class="blue"></hello-world>
  <hello-world class="green"></hello-world>

  <!--模板部分-->
  <template id="hello">
    <style>
      p {
        font-size: 16px;
        margin: 0
      }

      :host {
        display: block;
        color: red;
        border: 1px solid #333;
        width: 350px;
        margin: 30px
      }
      :host(.blue) {
        color: blue;
        font-size: 24px;
      }

      :host(.green) {
        color: green;
        font-size: 34px;
      }
    </style>
    <div class="hello">
      hello world
      <p part="age-part">我的年龄是:22</p>
      <p part="name-part">我的名字是:hockor </p>

    </div>
  </template>

</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.5.3/js/bootstrap.min.js"></script>
<script>
  class HelloWorld extends HTMLElement {
    constructor () {
      super()
      const shadow = this.attachShadow({
        mode: 'open'
      })
      const div = document.getElementById('hello')
      shadow.appendChild(div.content.cloneNode(true))
    }
  }

  customElements.define('hello-world', HelloWorld)
</script>

demo 地址:https://604n8h.csb.app/6.css-part.html

在这里我们可以使用该全局属性对外暴露我们一些 dom,由外部使用 CSS 来进行控制,非常的灵活。

总结

好了,本文的内容到这里差不多就结束了,我们看到在本文我们主要讲了 web components 中的生命周期和 css 控制,有了这 2 个我们的组件才能更加灵活,后续我们会继续将组件的事件交互和在实际项目中的运用。同时目前市面上也有一些基于 Web Component 标准开发的组件库,比如

感兴趣的同学可以去看看。


原创不易,欢迎关注公众号《hockor》,查看更多内容~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值