web components

什么是 Web Components

2011年 谷歌就提出 web components
2015 年 web components 才开始能用,只有Chrome浏览器支持
web components 浏览器原生组件化,不依赖框架
safari 2017 年实现一部分
Firfox 2018 年实现

Vue cli 已经实现编译成 web components 的功能,实际上 vue 在很大程度上参照了 web components 的实现,vue 可以说是进阶版的 web components

web components 包含技术

can i use web components

Web Components包含三种主要技术:

HTML Templates

熟悉 vue 的开发者对 template 模板这个概念会很熟悉了,模板、组件方便重用
<template><slot> 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用,和vue很像

自定义组件

<template>
   <span>
     <!-- slot 用法也是和vue的slot用法一样 -->
     <slot></slot>
   </span>
</template>

<script>
const dom = document.currentScript.ownerDocument.querySelector('template')
 customElements.define('my-span', class extends HTMLElement {
      constructor() { // 初始化阶段
        super()
        // 相当于vue的setup
        this.appendChild(dom)
      }
    })
</script>

<style>
span {
  color: green;
}
</style>

在html页面中使用

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- link引用组件,可能会不生效,因为 HTML Imports 🈲 被废弃 -->
  <link rel="import" href="./span.html" /> 
</head>

<body>
  <!-- 在页面中使用自定义的标签 -->
  <my-span>123</my-span>
  <!-- 还可以用is属性,和vue里的类似 -->
  <span is="my-span">456</span>
</body>

</html>

使用 HTML Modules

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script type="module">
    import MySpan from './span.html'
  </script>
  <!-- 在页面中使用自定义的标签 -->
  <my-span>123</my-span>
  <!-- 还可以用is属性,和vue里的类似 -->
  <span is="my-span">456</span>
</body>

</html>

出现报错,目前还未支持html的 module scripts

Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.

Custom Elements

自定义元素,开发者可以创建自己的DOM元素,扩展 HTMLElement 接口,继承自 HTMLElement, 并自定义一些 JS 逻辑。
一般继承自 HTMLElement 但是如果想要扩展某个元素,则应该继承该元素,例如想要自定义 input,则需要继承 input

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>web-components</title>
</head>
<body>
  <script>
    // 创建一个 <a-b-c> 的元素,名为 el
    const el = document.createElement('a-b-c')

    // 定义一个名为 <a-b-c> 的组件
    customElements.define('a-b-c', class extends HTMLElement {})

    // 获取 <a-b-c> 组件的构造函数
    customElements.get('a-b-c')

    // 升级我们之前创建的 el 元素
    customElements.upgrade(el)

    // 当 <a-b-c> 组件定义后
    customElements.whenDefined('a-b-c').then(() => { /* 当 <a-b-c> 组件定义后的回掉函数 */ })
  </script>
</body>
</html>

Shadow DOM

一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突

HTML Imports (废弃)

之前 HTML imports 也是其中的一部分,但对于 HTML Modules (ES6),HTML Imports 已经被废弃

生命周期


<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <iframe src="./iframe.html"></iframe>
  <life-cycle color="purple"></life-cycle>

  <script>
    const iframe = document.querySelector('iframe')
    iframe.onload = () => {
      const h1 = iframe.contentDocument.querySelector('h1')
      document.body.append(document.adoptNode(h1))
    }
    customElements.define('life-cycle', class extends HTMLElement {
      getColor() {
        return this.getAttribute('color')
      }
      setColor(value) {
        this.setAttribute('color', value)
      }
      static observedAttributes = ['color']

      constructor() { // 初始化阶段
        super()
        // 相当于vue的setup
        this.innerHTML = '<h1>组件</h1>'
      }
      connectedCallback() { // 挂载阶段
        // 相当于vue的mouted
      }
      disconnectCallback() { // 卸载阶段
        // 相当于vue的unmounted
      }
      adoptedCallback() { // 收养阶段
        // this.innerHTML = '<h1>来自iframe的组件</h1>'
      }
      attributeChangedCallback(name, oldValue, newValue) { // 属性改变后的阶段
        // attribute 特性
        // proporty 属性
        if (name === 'color') {
          this.style.color = newValue
        }
      }
    })
  </script>


</body>

</html>

使用

web components 是浏览器原生组件,所以是不依赖框架,可以在原生、vue、react等都可以使用,在react中可直接使用,vue、vite中需要声明是自定义元素
react组件都是大写开头驼峰命名的组件,原生组件是必须小写且以-中划线连接,所以不需要做识别处理,原生组件不会被识别为react组件,vue 则需要指定哪些是自定义的原生组件,否则会根据vue的组件编译规则来查找,出现找不到的情况

fancy-components 是用 web components 实现的组件,使用以此为例

原生

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>web-components</title>
</head>
<body>
<fc-3d-btn></fc-3d-btn>

<script type="module">

import { Fc3DBtn } from 'https://unpkg.zhimg.com/fancy-components'

// new 就相当于全局注册了这个组件,相当于 Vue 的 Vue.component('Fc3DBtn', 组件)
new Fc3DBtn()
</script>
</body>
</html>

react

src/index.js 入口文件里注册,使用就直接用对应的标签

import {
  FcTypingInput
} from 'fancy-components'

new FcTypingInput()

vue

main.js 入口文件里注册,使用就直接用对应的标签,注册使用同上
vue.config.js 添加额外配置

module.exports = {
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options => {
        options.compilerOptions = {
          ...(options.compilerOptions || {}),
          isCustomElement: tag => tag.startsWith('fc-') // 指定以fc开头的是自定义元素(原生组件),不是vue的组件
        }
        return options
      })
  }
}

vite

main.js 入口文件里注册,使用就直接用对应的标签,注册使用同上
vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue({
    template: {
      compilerOptions: {
        isCustomElement: tag => tag.startsWith('fc-')
      }
    }
  })]
})

兼容

由于 web components 各个浏览器的支持还不是特别好,如果想要使用,还是需要polyfills来做兼容
@webcomponents/webcomponentsjs 这个库可以用来做兼容

posted @ 2021-07-08 17:53  c-137Summer  阅读(698)  评论(0编辑  收藏  举报