一、什么是 Web Component
Web Component 是一种 W3C标准 支持的 组件化方案,通过它,我们可以编写可复用的 组件,同时,我们也可以对自己的组件做更精细化的控制。正如 PWA 一样,他并非一项单一的技术,而是由三项技术组成:
-
Custom elements:自定义元素,通过使用对应的 api,用户可以在不依赖框架的情况下,开发原生层面的自定义元素,最关键的是,它将包含独立的生命周期,以及提供了自定义属性的监听。这就意味着它也同样具备了较高的可操作性。 =
-
Shadow DOM : 影子 dom(最大的特点是不暴露给全局),你可以通过对应的 api,将 shadow dom 附加给你的自定义元素,并控制其相关功能。利用 shadow dom 的特性,起到隔离的作用,使特性保密,不用再担心所编写的脚本及样式与文档其他部分冲突。
-
HTML templates: 通过template、slot去实现内容分发。可以回忆一下 vue 的插槽(slot)和 react 的 props.children。
二、Custom elements
<body>
<script type="module" src="./main.js"></script>
<my-div></my-div>
</body>
// 继承 HTMLElement 类
class MyDiv extends HTMLElement {
constructor() {
super()
const container = document.createElement('div');
container.innerHTML = `
<style>
div{
width: 200px;
height: 200px;
border: 1px solid #000;
}
</style>
<div>
<h3 style='color: pink;'>测试shadowDom</h3>
<slot></slot>
</div>`
this.appendChild(container)
}
}
// 名字规范必须小写,且有一个以上的 '-'
customElements.define('my-div', MyDiv);
上面已经实现了最基础的 DOM 结构了,但你会发现外层的css会影响到组件内部的样式的问题
三、Shadow DOM
this.attachShadow({ mode: 'open' })
this.shadowRoot.appendChild(container) 或者
this.shadowRoot.innerHTML = `...`
四、HTML templates
这样一行一行地生成 DOM 结构不仅写的累,读的人也很难一下子看明白。为了解决这个问题,我们可以使用 HTML 模板 。直接在 HTML 里写一个 <template> 模板:
<body>
<script type="module" src="./main.js"></script>
<my-html-templates></my-html-templates>
<template id="my-html-templates">
<div class="container">
<img class="image" src="https://img1.baidu.com/it/u=3009731526,373851691&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500" alt="">
<h3 class="title">切尔诺贝利</h3>
</div>
</template>
</body>
class MyHtmltemplates extends HTMLElement {
constructor() {
super()
const templateElem = document.getElementById('my-html-templates')
const clonedElem = templateElem.content.cloneNode(true)
this.appendChild(clonedElem)
}
}
customElements.define('my-html-templates', MyHtmltemplates);
五、slot(类似vue的插槽)
<my-html-templates>
<div slot='title'>测试slot</div>
</my-html-templates>
<template id="my-html-templates">
<div class="container">
<img class="image" src="https://img1.baidu.com/it/u=3009731526,373851691&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500" alt="">
<h3 class="title">切尔诺贝利</h3>
<slot name="title"></slot>
</div>
</template>
六、交互
原生版本的简单交互:
<body>
<my-counter count="100"></my-counter>
<script>
// 监听传递事件countFn
(function(){
window.addEventListener('countFn', (e) => {
console.log(e)
alert(e.type);
})
}())
</script>
</body>
class Counter extends HTMLElement {
// 监听属性变化
static get observedAttributes() {
return ['count']
}
// 属性变化调用
attributeChangedCallback(attr, oldVal, newVal) {
if (attr === 'count') {
this.btn.textContent = newVal;
}
}
// 获取count
get count() {
return this.getAttribute('count') ? this.getAttribute('count') : 10
}
// 设置count值
set count(count) {
return this.setAttribute('count', count)
}
constructor() {
super();
// 设置为open才可以添加元素
this.attachShadow({ mode: 'open' })
}
connectedCallback() {
this.render()
this.btn.addEventListener('click', () => {
this.count++
// 事件传递
window.dispatchEvent(new CustomEvent('countFn', { detail: { str: 123456789 } }))
})
}
render() {
// 挂载根dom
this.shadowRoot.innerHTML = `
<style>
button{
color:var(--my-color);
width: 200px;
height: 100px;
border: 1px solid #000;
}
</style>
<button>${this.count}</button>`
this.btn = this.shadowRoot.querySelector('button')
}
}
使用谷歌的lit库
class MyLitCount extends LitElement {
static properties = {
count: {}
}
constructor() {
super()
this.count = 0
}
render() {
return html`
<style>
button{
width: 200px;
height: 100px;
border: 1px solid #000;
}
</style>
<button @click=${() => this.count++}>${this.count}</button>`
}
}
七、Attribute 和 Propertie
两个对象中都有count属性,在lit 中Attributes会被转成Property,
Attribute: a标签 中href就是Attribute, dom标签直接声明的属性, 仅支持string,number,boolean数据类型
Property : document.getElementById('a').title 中的title就是Property, 拿到dom元素设置的属性为Property, 可传递复杂的数据类型
Property涵盖范围 >Attributes涵盖范围
八、生命周期
connectedCallback:当 custom element首次被插入 DOM 时,被调用。
disconnectedCallback:当 custom element 从 DOM 中删除时,被调用。
adoptedCallback:当 custom element 被移动到新的文档时,被调用, 这特别适用于 iFrame。
attributeChangedCallback: 当 custom element 增加、删除、修改自身属性时,被调用
九、CSS传递
修改Shadow DOM ****的样式
index.html文件
<style>
:root {
--my-color: red;
}
</style>
mian.js 文件
<style>
color:var(--my-color)
</style>
十、vue中使用Web Component
需要过滤掉Web Component在一些打包工具的解析
webpack配置
// vue.config.js
module.exports = { chainWebpack: config => { config.module .rule('vue') .use('vue-loader') .tap(options => ({ ...options, compilerOptions: { // 将所有带 ion- 的标签名都视为自定义元素 isCustomElement: tag => tag.startsWith('ion-') } })) } }
vite配置
// vite.config.js
import vue from '@vitejs/plugin-vue'
export default { plugins: [ vue({ template: { compilerOptions: { // 将所有带短横线的标签名都视为自定义元素 isCustomElement: (tag) => tag.includes('-') } } }) ] }
十一、相关
Web Component : developer.mozilla.org/zh-CN/docs/…
谷歌lit地址: github.com/lit/lit
vue-lit地址: github.com/yyx990803/v…
组件库:
来自微软:learn.microsoft.com/en-us/fluen…
web-components.carbondesignsystem.com/?path=/stor…
vscode风格:bendera.github.io/vscode-webv…