简介
在我们的日常工作中,'组件'一词很是熟悉。无论是在 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
关注得物技术,携手走向技术的云端
评论