一、Web Components是什么
目前,我们的组件化开发,主要是借助框架来实现的,比如vue/react这些。 那么目前,一套新的技术已经在各个主流浏览器有了不错的支持, 那么就是今天所讲的Web Components
技术。这是一种独立的技术,它不是一个api,而是通过三个关键的技术组成, 分别是: custom elements
、 shadow dom
、 HTML templates
。 通过使用,可以做到各个组件间css、js、html都能做到有效隔离,给开发带来极大的便利。
我们目前的开发,越来越向组件化发展, 如果我们使用这套新技术,因为是原生浏览器支持的,所以,运行效率肯定要比框架要快的多。那么我们如果要进行技术选型, 需要先知道我们的使用场景,比如说是TO B类后台管理系统,那么我们可以有效规定用户使用的浏览器,那么这种情况是比较适合的。
先看下目前的支持程度:
主流浏览器的支持程度还是很不错的。(IE马上退出历史舞台了,所以在不考虑IE的项目中格外合适)
二、开始学习
2.1 创建一个自定义标签,从P标签继承
先创建一个js文件:
// custom-elements.js
class WordCount extends HTMLParagraphElement {
constructor() {
super();
}
// 目前只是新建了一个自定义元素,并没有使用shadow dom,所以样式并不会做到隔绝
connectedCallback() {
console.log('首次被插入dom时触发');
this.render();
}
disconnectedCallback() {
console.log('从dom中删除的时候触发');
}
attributeChangedCallback() {
console.log('当 custom element增加、删除、修改自身属性时,被调用');
}
render() {
const count = this.getAttribute('count');
const price = 20 * count;
this.innerHTML = `<p>选择的数量是${count}, 总价格是${price}</p>`;
}
}
// 创建一个自定义元素,类对象是WordCount, 继承自<p>元素
customElements.define('word-count', WordCount, {extends: 'p'});
在创建的时候,有三个重要的生命周期:
- connectedCallback 首次被插入dom中的时候触发,所以只会触发一次
- disconnectedCallback 从dom中删除的时候触发, 所以也只会触发一次
- attributeChangedCallback当custom elements 增加、删除、修改自身属性的时候,会被触发
html的部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>web components</title>
<style>
#title {
font-size: 24px;
color: #333;
height: 60px;
line-height: 60px;
}
p {
color:aquamarine;
font-size: 30px;
}
</style>
<script src="./custom-elements.js"></script>
</head>
<body>
<div id="title">custom Component</div>
<p is="word-count" count="10"></p>
</body>
</html>
这样,把js文件引入了,并且在p标签上,我们自定义了一个叫is
和 count
的属性。
看一下最终浏览器的渲染结果:
目前有以下基本结论:
- 我们创建的自定义组件已经加载到了浏览器中
- 组件的加载是在p标签下面的,因为我们这个组件继承自P标签
- 外层的css属性能够影响自定义组件中的样式
2.2 自定义一个html组件,并引入shadow
现在创建一个完全自定义的组件,跟vue或react的自定义组件一样,并引入样式隔离
// shadow-dom.js
class PopupInfo extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
// 具体代码逻辑
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('span');
wrapper.setAttribute('class', 'wrapper');
const icon = document.createElement('span');
icon.setAttribute('class', 'icon');
icon.setAttribute('tabindex', 0);
const info = document.createElement('span');
info.setAttribute('class', 'info');
// 获取text属性上的内容,并添加到一个span标签内
const text = this.getAttribute('text');
info.textContent = text;
// 插入icon
let imgUrl;
if(this.hasAttribute('img')) {
imgUrl = this.getAttribute('img');
} else {
imgUrl = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2F54%2F79%2F83%2F547983bb743d816230e483da74bccbe1.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631007635&t=fafafd459d7b1f23c5cfa51812c9e5fe';
}
const img = document.createElement('img');
img.src= imgUrl;
icon.appendChild(img);
// 创建css,并创建到shadow dom 上
const style = document.createElement('style');
style.textContent = `
.wrapper {
display: inline-block;
width: 700px;
height: 500px;
background: #f5f5f5;
font-size: 20px;
}
.icon {
display: inline-block;
height: 100px;
}
.icon img {
height: 100%;
}
`;
shadow.appendChild(style);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);
}
}
customElements.define('popup-info', PopupInfo);
上面逻辑中,关键是创建了一个shadow, this.attachShadow({mode: 'open'});
, shadow的解释attachShadow MDN
其中提到我认为比较重要的两点:
- 些元素不能使用 shadow DOM(例如
<a>
) - mode (模式)
open
shadow root元素可以从js外部访问根节点close
拒绝从js外部访问关闭的shadow root节点
我们创建了几个标签,比如span、img,和一些属性。
在看看 html的操作:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadow DOM</title>
<style>
#title {
font-size: 24px;
color: #333;
height: 60px;
line-height: 60px;
}
.span {
font-size: 30px;
color: green;
}
</style>
<script src="./shadow-dom.js"></script>
</head>
<body>
<div id="title">custom Component + shadow Dom</div>
<popup-info text="Your card validation code (CVC)
is an extra security feature — it is the last 3 or 4 numbers on the
back of your card."></popup-info>
</body>
</html>
执行一下, 效果是:
可以看出,组件顺利的渲染了,并且外边的样式也不会影响里面的, 使用 custom element
+ shadow
已经做到自定义组件+样式隔离了。
2.3 加入HTML templates
上面的两步其实已经把精髓展示了。但是,还缺一步,就是我们对模板的处理都是在js中处理的,开发起来比较麻烦,也不容易进行扩展和维护。 所以template能够有效的解决这个问题。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>web components使用template模板</title>
<style>
#title {
font-size: 24px;
color: #333;
height: 60px;
line-height: 60px;
}
p {
color:aquamarine;
font-size: 30px;
}
</style>
</head>
<body>
<div id="title">custom Component</div>
<template id="temp">
<slot><p>测试测试</p></slot>
</template>
<my-paragraph>
<p>新的slot内容,把默认数据替换</p>
</my-paragraph>
</body>
<script src="./template.js"></script>
</html>
template标签不会展示到页面上,需要获取到其中的内容,然后再进行展示
class MyParagraph extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
console.log('首次被插入dom时触发');
const target = document.getElementById('temp');
const content = target.content;
const shadowRoot = this.attachShadow({mode: 'open'})
.appendChild(content.cloneNode(true));
}
}
customElements.define('my-paragraph', MyParagraph);
还有一种template的插入方式,是使用 link标签, 进行HTML imports。 比如:
<link rel="import" href="./contents.html">
但是这个功能已经不被支持,很可能在后面被删除,具体见 HTML_Imports
以及:
所以,大家不要再继续用 HTML-imports 的方式了。
以上就是Web Components的内容。 这里需要再指明的是, 并不是有了这个功能,就能抛弃vue等框架,mvvm框架的数据驱动视图,是无法直接用Web Components实现的。 两者结合起来,会迸发出新的可能性。