写点什么

【得物技术】Web Components 初探

用户头像
得物技术
关注
发布于: 2021 年 02 月 26 日
【得物技术】Web Components 初探

简介


在我们的日常工作中,'组件'一词很是熟悉。无论是在 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 伪类


  • :defined:匹配任何已定义的元素,包括内置元素和自定义的元素。

  • :hostShaow Host,组自定元素挂载的节点。


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

关注得物技术,携手走向技术的云端


发布于: 2021 年 02 月 26 日阅读数: 14
用户头像

得物技术

关注

得物APP技术部 2019.11.13 加入

关注微信公众号「得物技术」

评论

发布
暂无评论
【得物技术】Web Components 初探