1. 什么是Web Components?它的出现原因?为什么要学习它?
Web Components 是一个浏览器原生支持的组件化方案,允许你创建新的自定义、可封装、可重用的HTML 标记。不用加载任何外部模块,直接就可以在浏览器中跑。
它的出现原因?因为早期组件生态很乱,有各种各样的框架和库,都有各自的规范,导致一个团队切换框架很困难 。为了解决这种分化的形式,让 Web 组件模型统一化,所以才有了Web Components规范的出现。目标是提供一种权威的、浏览器能理解的方式来创建组件。
为什么要学习它?用一句话总结就是回顾历史展望未来。
2011年提出Web Components概念,React诞生
2013年 Chrome 和 Opera 又联合提出了推出的 V0 版本的 Web Components 规范,React开源
2014年Vue诞生
2016年Web Components 推进到了 V1 版本
由于浏览器兼容性、和主流框架开发效率等等问题导致现在几乎使用不到它,但我们可以学习它的思想,也许未来就会变的有用?
2. Web Components 由三种技术组成
Custom Elements
可以创建一个自定义标签。根据规范,自定义元素的名称必须包含连词线”-“,用与区别原生的 HTML 元素。
<body>
<user-card></user-card>
<script>
class UserCard extends HTMLElement {
constructor() {
super();
var el = document.createElement('p');
el.classList.add('name');
el.innerText = 'User Name';
this.append(el);
}
}
window.customElements.define('user-card', UserCard);
</script>
</body>
class UserButton extends HTMLButtonElement {
constructor() {
super();
}
}
customElements.define('user-button', UserButton, { extends: "button" });
<button is="user-button">
</button>
使用生命周期回调函数
在custom element的构造函数中,可以指定多个不同的回调函数,它们将会在元素的不同生命时期被调用:
connectedCallback
:当 custom element首次被插入文档DOM时,被调用。disconnectedCallback
:当 custom element从文档DOM中删除时,被调用。adoptedCallback
:当 custom element被移动到新的文档时,被调用。attributeChangedCallback
: 当 custom element增加、删除、修改自身属性时,被调用。
Shadow DOM
Shadow DOM是对DOM的一个封装。可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。
<style>
p {
color: blueviolet;
}
</style>
<p>不会影响shadow样式</p>
<script>
const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'closed'});// header.shadowRoot === null
// const shadowRoot = header.attachShadow({mode: 'open'});// header.shadowRoot === shadowRoot #document-fragment
shadowRoot.innerHTML = '<p>Hello Shadow DOM</p>';
document.querySelector('body').append(header);// shadowRoot.host === header
</script>
修改shadow dom内部样式:
templates and slots
可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
- 被使用前不会被渲染。
- 被使用前对页面其他部分没有影响,脚本不会运行,图像不会加载,音频不会播放。
<body>
<p>会影响外部样式</p>
<template id="my-paragraph">
<style>
p{color: red;}
</style>
<p>My paragraph</p>
</template>
<my-paragraph></my-paragraph>
<script>
customElements.define('my-paragraph',
class extends HTMLElement {
constructor() {
super();
let template = document.getElementById('my-paragraph');
let templateContent = template.content.cloneNode(true);
this.appendChild(templateContent);
}
})
</script>
</body>
slot的使用:
<body>
<style>
p{color: blueviolet;}
</style>
<p>会影响外部样式</p>
<template id="my-paragraph">
<style>
p{color: red;}
</style>
<p>My paragraph</p>
<slot name="my-text">My default text</slot>
</template>
<my-paragraph>
<p slot="my-text">slot text</p>
</my-paragraph>
<script>
customElements.define('my-paragraph',
class extends HTMLElement {
constructor() {
super();
let template = document.getElementById('my-paragraph');
let templateContent = template.content.cloneNode(true);
this.attachShadow({mode: 'open'}).appendChild(templateContent);
}
})
</script>
</body>
3. 实现一个计数器组件
<body>
<template id="button-counter">
<style>
p{color: blueviolet;}
</style>
<span id="counter"></span>
<button id="button"></button>
</template>
<button-counter counter="0" label="增加1"></button-counter>
<script>
document.querySelector('button-counter').buttonCounter=(val)=>{}
// document.querySelector('button-counter').addEventListener('test', (val)=>{
// console.log(val.detail.counter);
// })
class ButtonCounter extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
let el = document.querySelector('#button-counter').content.cloneNode(true);
shadowRoot.appendChild(el);
this.$counter = shadowRoot.querySelector('#counter');
this.$button = shadowRoot.querySelector('#button');
this.$button.addEventListener('click', () => {
this.counter++;
this.buttonCounter(this.counter);
// this.dispatchEvent(
// new CustomEvent('test', {
// detail: {counter: this.counter},
// })
// );
});
}
get label() {
return this.getAttribute('label');
}
set label(value) {
this.setAttribute('label', value);
}
get counter() {
return this.getAttribute('counter');
}
set counter(value) {
this.setAttribute('counter', value);
}
static get observedAttributes() {
return ['label', 'counter'];
}
attributeChangedCallback(name, oldVal, newVal) {
console.log('attributeChangedCallback');
this.render();
}
render() {
this.$button.innerHTML = this.label;
this.$counter.innerHTML = `You clicked me ${this.counter} times`;
}
connectedCallback() {
console.log('connectedCallback');
}
disconnectedCallback() {
console.log('disconnectedCallback');
}
adoptedCallback() {
console.log('adoptedCallback');
}
}
customElements.define('button-counter', ButtonCounter)
</script>
</body>
4. Web Components vs Vue Components
Vue | Web Component |
---|---|
data | 实例属性 |
props | attributes |
watch | observedAttributes attributeChangedCallback |
computed | getters |
methods | class methods |
mounted | connectedCallback |
destroyed | disconnectedCallback |
style scoped | template中的style |
template | template |
computed: {
name() {
return this.planet.name || '';
},
}
get name() {
return this.planet.name || '';
}
components: {
"planet-summary": PlanetSummary
}
customElements.define("planet-summary", PlanetSummary);
watch: {
altitude(newValue, oldValue) {
...
}
}
static get observedAttributes() {
return ['altitude'];
}
attributeChangedCallback(name, oldValue, newValue) {
}
性能比较
从上图可以看出在浏览器中组件越多,vue的js计算时间比web components越少,性能越好。原因大概是因为
vue是通过 模板->虚拟DOM->真实DOM
来实现自定义组件的。
web components是在浏览器中通过 自定义组件->Shadow Dom
实现自定义组件的。
导致了浏览器中vue的性能比web components性能优秀。
5. 优点与缺点
- 浏览器原生支持,不需要引入额外的第三方库
- 语义化
- 复用性,移植性高
- 不同团队不同项目可以共用组件
缺点
- 需要操作DOM
- 目前浏览器兼容性、性能方面不够友好
- 和外部css交互比较难
6. 浏览器兼容
兼容性不好的浏览器可以通过引入webcomponentsjs来polyfills。
<!-- load webcomponents bundle, which includes all the necessary polyfills -->
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
<!-- load the element -->
<script type="module" src="my-element.js"></script>
<!-- use the element -->
<my-element></my-element>
7. 基于web components的框架
LitElement
LitElement是一个快速、轻量级的 Web UI
框架。使用 lit-html 来渲染元素。
Polymer
Polymer是一款实用、基于事件驱动、封装性和交互性强的 Web UI
框架。
Omi
Omi是基于 Web 组件的跨框架跨平台框架 。移动端&桌面&小程序。