用 Web Components 实现一个 AppDrawer 组件

Web Components 和 vue、react 都是基于组件化的形式去书写页面。

Web Components 的主要作用及特点:

  1. 可以创建新的 html 标签、扩展 html 标记。
  2. 创建可复用的组件。
  3. 不依赖框架(但是需要浏览器支持),浏览器自带调试 Web Components 的能力。
  4. 与 Shadow DOM 配合使用更加强大(隔离DOM、作用域CSS、不用担心CSS命名重复)。

Drawer 组件

Demo

Html:

<div class="page">
  <div class="page__header">
    <!-- 设置组件open属性 -->
    <button class="page__header-button" onclick="drawer.setAttribute('open', '')"></button>
  </div>
  <div class="page__content"></div>

  <!-- 使用定义的组件 -->
  <app-drawer id="drawer">
    <ul class="page__drawer">
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>
  </app-drawer>
</div>

<!-- 定义组件的模板,html、css -->
<template id="app-drawer-template">
  <style>
    /* :host 用于设置根级组件样式,外部定义的样式会覆盖此处定义的样式 */
    :host {
      position: absolute;
      top: 0;
      left: 0;
      z-index: 2;
      width: 200px;
      height: 100%;
      background-color: #2d2d2d;
      transition: .3s ease;
    }

    :host(.close) {
      transform: translate3d(-100%, 0, 0)
    }
  </style>
  <!-- 内容插槽,和 Vue 类似 -->
  <slot></slot>
  <slot name="close">
    <button id="close">关闭</button>
  </slot>
</template>

Javascript:

  class AppDrawer extends HTMLElement {
    constructor() {
      super()
      this._handleClick = this._handleClick.bind(this)
      this.close()
      this._createShadowDOM()
      this._handleEvent('add')
    }
    // 设置需要检测的属性,可用于初始化组件状态
    static get observedAttributes() {
      return ['open']
    }
    set isOpen (value) {
      if (value) {
        this.setAttribute('open', '')
      } else {
        this.removeAttribute('open')
      }
    }
    get isOpen() {
      return this.hasAttribute('open')
    }
    // 在observedAttributes里设置了检测的属性,修改了就会调用,相当于vue watch
    attributeChangedCallback(name, oldValue, newValue) {
      this.toggle()
    }
    // 元素插入到dom后调用
    connectedCallback() {
    }
    // 元素移出dom后调用
    disconnectedCallback() {
      this.handleEvent('remove')
    }
    _createShadowDOM() {
      const shadowRoot = this.attachShadow({ mode: 'open' })
      const template = document.querySelector('#app-drawer-template')
      const instance = template.content.cloneNode(true)
      shadowRoot.appendChild(instance)
    }
    _handleEvent(action) {
      // 通过 this.shadowRoot.querySelector 选择组件里的元素
      const closeButton = this.shadowRoot.querySelector('#close')
      if (closeButton) {
        closeButton[`${action}EventListener`]('click', this._handleClick)
      }
    }
    _handleClick () {
      this.isOpen = false
    }
    open() {
      this.classList.remove('close')
    }
    close() {
      this.classList.add('close')
    }
    toggle() {
      if (this.isOpen) {
        this.open()
      } else {
        this.close()
      }
    }
  }
  // 定义组件
  window.customElements.define('app-drawer', AppDrawer)

总结

WebComponents 可以创建复用的组件。ShadowDom 隔离 dom、css 提高了可维护性,不用在担心命名重复的问题。所有的功能都是基于原生Api和浏览器实现的,不用配置各种npm包。但是在操作 dom 上还是和以前一样繁琐,需要 querySelector 获取 dom 元素,才能完成后面的操作,如果基于数据驱动来更新页面会比直接操作 dom 要更加简洁,可以少写很多代码。

相关资料:

https://developers.google.com/web/fundamentals/web-components/customelements?hl=zh-cn