简介
在我们的日常工作中,'组件'一词很是熟悉。无论是在 react、vue 项目中,都经常会去封装一些自定义个性化的组件,来达到某些特定的功能,但是这些组件都需要外部模块的支持。Web Components API 便提供了一种方式,在不依赖外部模块的情况下封装自定义的组件。
Web Components API 介绍
它由三项主要技术组成:
Custom elements(自定义元素):一组 JavaScript API,允许您定义 custom elements 及其行为,然后可以在您的用户界面中按照需要使用它们。
Shadow DOM(影子 DOM):一组 JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
HTML templates(HTML 模板): <template> 和 <slot> 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
Custom elements(自定义元素)
自定义元素 Custom elements 是 Web components 技术的核心。
1. CustomElementRegistry
CustomElementRegistry:包含自定义元素相关功能。
CustomElementRegistry.define()方法可以用来注册新的自定义元素。window.customElements 返回的就是 CustomElementRegistry 对象的引用。
注意点:
自定义元素类的基类是 HTMLElement,所以继承了 HTML 元素的特性。也可以继 HTMLElement 的子类,比如 HTMLDivElement 、HTMLTableElement 、HTMLButtonElement 等。
自定义元素标签的名称必须包含连字符 - ,用来和内置的 HTML 标签做区别。
浏览器如果没有解析到自定义元素,会当做空的 div 元素处理。
class MyComponent extends HTMLElement {
constructor() {
super();
// ...
}
}
// 注册一个自定义元素
window.customElements.define('my-component', MyComponent);
复制代码
2. 创建自定义内置元素的扩展
is 属性:HTML 的全局属性,使用 is 属性可以把一个 HTML 的内置属性标记成一个已注册的自定义内置元素。
class NewButton extends HTMLButtonElement {
// ...
}
// 注册时,提供元素的扩展
window.customElements.define('new-button', NewButton, { extends: 'button' })
// 使用时
<button is="new-button">NewButton</button>
复制代码
3. 生命周期
自定义组件的特殊回调函数:
class MyComponent extends HTMLElement {
constructor() {
super()
}
connectedCallback(){
// 当自定义元素第一次被连接到文档DOM时被调用
}
disconnectedCallback(){
// 当自定义元素与文档DOM断开连接时被调用
}
adoptedCallback(){
// 当自定义元素被移动到新文档时被调用
}
attributeChangedCallback(){
// 当自定义元素的一个属性被增加、移除或更改时被调用
}
}
复制代码
4. 自定义方法和属性
自定义元素就是 javascript 的类,因此自定义元素的方法和属性的用法和 class 一样。
class MyComponent extends HTMLElement {
constructor() {
super()
}
// 自定义方法
hello() {
const name = this.getAttribute('name')
console.log('hello' + name)
}
// 也可以设置取值器和赋值器
set name(name) {
const oldName = this.getAttribute('name')
if(name !== oldName) {
this.setAttribute('name', name)
}
}
get name() {
return this.getAttribute('name')
}
}
window.customElements.define('my-component', MyComponent);
// 使用
<my-component name="zhangsan"></my-component>
const el = document.querySelector('my-component');
el.hello();
复制代码
5. css 伪类
Shadow DOM(影子 DOM)
如果我们希望自定义元素的内部代码不允许被外部访问到,我们可以设置 Shadow DOM 来将其与外部隔离,这样外部的设置无法影响到其内部,而内部的设置也不会影响到外部。
Shadow DOM 特有的术语:
Shadow host:一个常规 DOM 节点,Shadow DOM 会被附加到这个节点上。
Shadow tree:Shadow DOM 内部的 DOM 树。
Shadow boundary:Shadow DOM 结束的地方,也是常规 DOM 开始的地方。
Shadow root: Shadow tree 的根节点。
Shadow DOM 最大的好处:
向用户隐藏细节,直接提供组件。
可以封装内部样式表,不会影响到外部。
例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Web Components</title>
<style>
.btn {
color: #000;
font-size: 30px;
}
</style>
</head>
<body>
<button class="btn">获取shadowRoot</button>
<my-component></my-component>
</body>
<script>
class MyComponent extends HTMLElement {
constructor() {
super();
var con = document.createElement('div');
con.classList.add('btn');
con.innerHTML = "自定义元素";
let style = document.createElement('style');
style.innerText = '.btn { font-weight: 600; color: red;}';
this.appendChild(style);
this.appendChild(con);
}
}
window.customElements.define('my-component', MyComponent);
</script>
</html>
复制代码
1. 不使用 shadow Dom
在不使用 shadow Dom 的时,内外部样式相互影响,优先级受选择器和加载顺序影响。
2. 使用 shadow Dom
使用 shadow Dom 的时,内外部样式互不影响。
class MyComponent extends HTMLElement {
constructor() {
super();
// 使用 shadow dom
var shadow = this.attachShadow( { mode: 'open' } );
var con = document.createElement('div');
con.classList.add('btn');
con.innerHTML = "自定义元素";
let style = document.createElement('style');
style.innerText = '.btn { font-weight: 600; color: red;}';
shadow.appendChild(style);
shadow.appendChild(con);
}
}
复制代码
3. mode 设置
通过设置 mode 为 open 或 closed 能够控制是否可以在外部访问到组件的 shadowRoot。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Web Components</title>
<style>
.btn {
color: #000;
font-size: 30px;
}
</style>
</head>
<body>
<button class="btn">获取shadowRoot</button>
<my-component></my-component>
</body>
<script>
class MyComponent extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow( { mode: 'open' } );
var con = document.createElement('div');
con.classList.add('btn');
con.innerHTML = "自定义元素";
let style = document.createElement('style');
style.innerText = '.btn { font-weight: 600; color: red;}';
shadow.appendChild(style);
shadow.appendChild(con);
}
}
window.customElements.define('my-component', MyComponent);
const btn = document.querySelector('.btn')
btn.addEventListener('click',() => {
const el = document.querySelector('my-component');
// 获取 shadowRoot
console.log(el.shadowRoot)
})
</script>
</html>
复制代码
当 mode:'open'时,点击获取 shadowRoot 按钮:
当 mode:'closed'时,点击获取 shadowRoot 按钮:
HTML templates(HTML 模板)
1、template 模版
上文中在 JavaScript 中撸 dom、style 是很麻烦的一件事。Web Components API 提供了<template>标签,包含一个 HTML 片段,不会在文档初始化时渲染。但是可以在运行时使用 JavaScript 显示。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<my-component
title="这是标题"
content="这是内容这是内容这是内容这是内容这是内容这是内容"
></my-component>
<template id="template">
<style>
:host {
display: flex;
align-items: center;
width: 450px;
height: 180px;
background-color: #ccc;
border-radius: 8px;
}
.container {
margin: 20px;
height: 150px;
}
.container > .title {
font-size: 24px;
line-height: 30px;
margin-bottom: 15px;
}
.container > .content {
padding: 10px;
font-size: 14px;
}
</style>
<div class="container">
<p class="title"></p>
<p class="content"></p>
</div>
</template>
</body>
<script>
class MyComponent extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow( { mode: 'closed' } );
var template = document.getElementById('template');
var content = template.content.cloneNode(true);
content.querySelector('.container>.title').innerText = this.getAttribute('title');
content.querySelector('.container>.content').innerText = this.getAttribute('content');
shadow.appendChild(content);
}
}
window.customElements.define('my-component', MyComponent);
</script>
</html>
复制代码
2、Slot
<slot> 元素,也叫插槽。作为 Web Components 技术的一部分,是 Web 组件内的一个占位符。该占位符可以在后期使用自己的标记语言填充,这样您就可以创建单独的 DOM 树,并将它与其它的组件组合在一起。插槽也分为默认插槽和具名插槽。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<my-component>
<div slot="left">left</div>
<div slot="right">right</div>
<div>content</div>
</my-component>
<template id="template">
<style>
:host {
display: flex;
align-items: center;
width: 480px;
height: 180px;
background-color: #ccc;
border-radius: 8px;
}
.container {
display: flex;
}
.container > .con {
height: 100px;
width: 100px;
flex-grow: 1;
font-size: 24px;
}
</style>
<div class="container">
<p class="con">
<slot name="left"></slot>
</p>
<p class="con">
<slot></slot>
</p>
<p class="con">
<slot name="right"></slot>
</p>
</div>
</template>
</body>
<script>
class MyComponent extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow( { mode: 'closed' } );
var template = document.getElementById('template');
var content = template.content.cloneNode(true);
shadow.appendChild(content);
}
}
window.customElements.define('my-component', MyComponent);
</script>
</html>
复制代码
参考文献
MDN Web Components:https://developer.mozilla.org/zh-CN/docs/Web/Web_Components
文|zhousibao
关注得物技术,携手走向技术的云端
评论