Web Components 入门

112 阅读2分钟

组件化是个老生常谈的话题了,React 和 Vue 都是组件框架。浏览器本身也有组件化方案。即浏览器原生组件Web Components。原生组件简单直接,不需要加载任何外部模块。下面介绍Web Components 相关的几种技术。Web Components 主要由四种技术组成

Custom Elements

自定义的 HTML 标签,也称为自定义元素(custom element)。是Web Components的核心技术。

  • 根据规范,自定义元素的名称必须包含连词线,用于区别原生的 HTML 元素。所以,<user-info>不能写成<userInfo>
  • 创建一个继承HTMLElement的类,注意类必须调用super();然后使用customElements.define()进行注册;我们就可以使用自定义元素了。
<body>
    <user-info></user-info>
    <script>
        class UserInfo extends HTMLElement {
            constructor() {
                super();
                
                var el = document.createElement('p');
                el.innerText = 'User Name';
                var el2 = document.createElement('p');
                el2.innerText = '132****1';
                this.append(el,el2);
            }
        }
        window.customElements.define('user-info', UserInfo);
    </script>
</body>

生命周期

构造函数中可以指定自定义元素的生命周期,将会在不同阶段调用。具体包括四个:

  • connectedCallback:当 custom element 首次被插入文档 DOM 时,被调用。
  • disconnectedCallback:当 custom element 从文档 DOM 中删除时,被调用。
  • adoptedCallback:当 custom element 被移动到新的文档时,被调用。
  • attributeChangedCallback: 当 custom element 增加、删除、修改自身属性时,被调用。
<script>
        class UserInfo extends HTMLElement {
            constructor() {
                super();
                
                var el = document.createElement('p');
                el.innerText = 'User Name';
                var el2 = document.createElement('p');
                el2.innerText = '132****1';
                this.append(el,el2);
            }
            connectedCallback() {
            }
        }
        window.customElements.define('user-info', UserInfo);
    </script>

template

使用 JavaScript 写 DOM 结构很麻烦,Web Components API 提供了<template>标签,可以在它里面直接使用 HTML,更加直观。

<template id="userInfoTemplate">
    <p class="name"></p>
    <p class="phone">132****1</p>
  </div>
</template>

改写一下自定义元素的类,为自定义元素加载<template>

<user-info
  name="User Name"
  phone="132****1111"
></user-info>
class UserInfo extends HTMLElement {
  constructor() {
    super();

    var templateElem = document.getElementById('userInfoTemplate');
    var content = templateElem.content.cloneNode(true);                               content.querySelector(".name").innerText = this.getAttribute("name");
    content.querySelector(".phone").innerText = this.getAttribute("phone");
    this.appendChild(content);
  }
}  
  • 获取到模板之后,克隆了它的所有子元素,克隆是为了复用,因为页面上的模板并不是一次性的,可能其他的组件也要引用。
  • this.getAttribute 可以获取到组件的传参。

样式

给自定义元素添加样式,组件的样式应该与代码封装在一起,只对自定义元素生效,不影响外部的全局样式。可以把样式写在<template>里面即可。


<template id="userInfoTemplate">
  <style>
   p{
       border-bottom: 1px solid #efefef;
   }
   
  </style>
  <p class="name">User Name</p>
  <p class="phone">132****1</p>
</template>

shadow Dom

Web Component 允许内部代码隐藏起来,这叫做 Shadow DOM,即这部分 DOM 默认与外部 DOM 隔离,内部任何代码都无法影响外部。

自定义元素的this.attachShadow()方法开启 Shadow DOM,详见下面的代码



class UserCard extends HTMLElement {
  constructor() {
    super();
    var shadow = this.attachShadow( { mode: 'closed' } );

    var templateElem = document.getElementById('userInfoTemplate');
    var content = templateElem.content.cloneNode(true);
    content.querySelector('.name').innerText = this.getAttribute('name');
    content.querySelector('.phone').innerText = this.getAttribute('phone');

    shadow.appendChild(content);
  }
}
window.customElements.define('user-card', UserCard);

上面代码中,this.attachShadow()方法将一个 shadow root 附加到一个元素上。它的参数mode 属性,值为 open 或 closed,表示 Shadow DOM 内的节点是否能被外部获取。

HTML Import

为了解决加载外部网页这个问题,而提出来的。但是兼容性不是很好。

function supportImport() {
  return 'import' in document.createElement('link');
}

if (supportImport()) {
  console.log('浏览器支持import特性');
} else {
  console.log('浏览器不支持import特性');
  // 引入polyfill
  var e = document.createElement('script');
  e.src = '[https://unpkg.com/@webcomponents/webcomponentsjs@2.0.0/webcomponents-bundle.js](https://unpkg.com/@webcomponents/webcomponentsjs@2.0.0/webcomponents-bundle.js)';
  document.body.appendChild(e);
}

HTML Import使得Web Component变得可分享了,其他人只要拷贝useInfo.html,就可以在自己的页面中使用了

<head>
  <link rel="import" href="userInfo.html">
</head>
<body> 
  <user-info
      name="User Name"
      phone="132****1111"
    ></user-info>
</body>