web components

662 阅读3分钟

更多文章

前言

10月份忙了一段业务,将自己理解的一些开发思想落地的业务中,也有了不少的收获。身为一个技术,自是有着一步跟不上步步跟不上的觉悟,so,马不停蹄的去了解了web components一下,说实话用下来感觉不是很顺手,主要还是诸多api不了

由来

三大框架vuereactangular并驾齐驱,但是有个问题就是组件之间不通用,很多优秀的ui库会做三个版本让开发者使用,解决了一些问题,但这样的做法无疑是对资源的浪费,而且维护成本高,需要对各个版本库单独维护,为了解决这样的问题,web components应运而生,让组件的变得更加通用,一次开发即可多处使用

核心概念

如果你使用过vue的模板语法,你会发现web components处处都有vue的影子,其实vue也有一部分是参考了web components,介绍几个重点概念:

  • Custom elements

即:自定义元素,web components允许我们自定义组件,就像是vue一样,写一个组件然后使用,如:

class XwButton extends HTMLElement { }
window.customElements.define('xw-button', XwButton)

定义完成后就可以在html中使用:

<xw-button></xw-button>
  • Shadow DOM

可以将组件与其他文档分离开来,作为独立的一部分,保持私有性,无需担心jscss与其他部分冲突

this._shadowRoot = this.attachShadow({ mode: 'closed' })
  • HTML templates

web components提供了<template><slot>两个标签,这两个标签只是标记模板,而slot功能类似vue中的插槽

其实还有很多api,参考web components

接下来用来一个demo来看一下web components是如何完成一个组件的

Button

demo

button组件写的比较简单,代码也有相应的注释,如下:

const buttonTemplate = document.createElement('template');
// 内容模板
buttonTemplate.innerHTML = `
  <style>
    :host {
      display: inline-block; 
      padding: 5px 15px;
      box-sizing:border-box; 
      border:1px solid var(--borderColor, #ccc); 
      font-size: 14px; 
      color: var(--fontColor,#333);  
      border-radius: var(--borderRadius,.25em); 
      cursor: pointer;
    }
    :host([type="primary"]) {
      background: #409EFF;
      border-color: #409EFF;
      color: #fff;
    }
    :host([type="success"]) {
      background: #67C23A;
      border-color: #67C23A;
      color: #fff;
    }
    :host([type="warning"]) {
      background: #E6A23C;
      border-color: #E6A23C;
      color: #fff;
    }
    :host([type="error"]) {
      background: #F56C6C;
      border-color: #F56C6C;
      color: #fff;
    }
    :host([disabled]) {
      opacity: .5;
      cursor: not-allowed;
    }
    :host([text]) {
      padding: 0;
      border: none;
      background: none;
    }
    :host([text][type="primary"]) {
      padding: 0;
      border: none;
      background: none;
      color: #409EFF;
    }
    :host([text][type="success"]) {
      padding: 0;
      border: none;
      background: none;
      color: #67C23A;
    }
    :host([text][type="warning"]) {
      padding: 0;
      border: none;
      background: none;
      color: #E6A23C;
    }
    :host([text][type="error"]) {
      padding: 0;
      border: none;
      background: none;
      color: #F56C6C;
    }
    .xw-button {
      background:none; 
      outline:0; 
      border:0; 
      width:100%;
      height:100%;
      padding:0;
      user-select: none;
      cursor: unset;
    }
  </style>
  <div
    class="xw-button"
  >
    <slot></slot>
  </div>
`

class XwButton extends HTMLElement {
  constructor() {
    super()
    // 创建影子DOM(与主文档DOM分开呈现)
    this._shadowRoot = this.attachShadow({ mode: 'open' });
    // 添加元素
    this._shadowRoot.appendChild(buttonTemplate.content.cloneNode(true))
  }
}
// 自定义元素
window.customElements.define('xw-button', XwButton)

class部分很好理解,css部分有一个点要解释一下就是:host可以理解为代表整个shadow Dom容器,全局不可以在使用通配符重写margingpadding样式,否则会影响shadow Dom容器内部样式

这里简单的写了个button,但说实话样式部分写起来蛮累的

Input

如下:

demo

input组件加了自定义事件和状态

  • 自定义事件
this.$input.addEventListener('input', e => {
  e.stopPropagation()
  const { target: { value } } = e
  // dispatchEvent用来分发事件, CustomEvent自定义时间,可以携带一个参数
  this.dispatchEvent(new CustomEvent('input', { detail: { value } }))
})

监听了input时间,并且抛出一个新的input事件,且将vue值传出去,在html中使用如下:

<!-- html -->
<xw-input oninput="onInput(event)"></xw-input>
<!-- js -->
<script>
  const onInput = (event) => console.log('input', event.detail)
</script>

vue中使用如下:

<template>
  <div>
    <xw-input id="webInput" defaultValue="123"></xw-input>
    {{ value }}
  </div>
</template>
<script>
export default {
  data() {
    return {
      value: '123'
    }
  },
  mounted() {
    document.getElementById('webInput').addEventListener('input', e => {
      const { target: { value } } = e
      this.value = value
    })
  }
}
</script>

其他常用方法

  • observedAttributes、attributeChangedCallback

observedAttributes返回一个数组,当数组中数据发生变化时在attributeChangedCallback中会监听到数据的变化、

class XwInput extends HTMLElement {
  constructor() {
    super()
  }

  get value() {
    return this.$input.value
  }

  set value(value) {
    this.$input.value = value
  }

  static get observedAttributes() {
    // 监听value属性的变化
    return ['value']
  }

  attributeChangedCallback(key, oldVal, newVal) {
    // value变化触发事件
    console.log(key, oldVal, newVal)
  }

}
  • connectedCallback

自定义元素初次连接到DOM时触发

优劣

  • 优势
  1. 原生,无需额外插件
  2. 无需编译
  3. 标准的css/js/html
  4. 跨平台、跨框架
  5. 维护成本低
  • 劣势
  1. 有兼容性,现在浏览器已支持
  2. 有学习成本,原生基础要扎实

结语

目前只是简单的做了尝试,了解了一些api,还是需要在业务开发中操刀一些(已经可以在生产在使用)