背景
在我们常用的 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 标准开发的组件库,比如
感兴趣的同学可以去看看。
|