什么是 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 包含技術
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 這個庫可以用來做兼容