Web Components 是什么
Web Components 是浏览器原生支持的组件化方案,允许你创建自定义、可封装的HTML 标记,使用时不用加载任何额外的模块。自定义组件和小部件基于 Web Components 标准构建,并可与任何支持 HTML 的 JavaScript 库或框架一起使用
Custom elements创建方式
HTML template模版创建
xml
<my-component id="myComponent"></my-component>
<template id="myTemplate">
<style>
h1 {
color: red
}
</style>
<h1 id="title">template 创建</h1>
</template>
<script>
class MyComponent extends HTMLElement {
constructor() {
super();
const template = document.querySelector('#myTemplate')
const content = template.content.cloneNode(true)
this.appendChild(content)
}
}
customElements.define('my-component', MyComponent)
</script>
Class函数内部创建
xml
<my-element></my-element>
<script>
class MyElement extends HTMLElement {
constructor() {
super();
const style = document.createElement('style');
style.textContent = `
p {
color: saddlebrown;
}
`;
const wrapper = document.createElement('p');
wrapper.textContent = '通过class内创建';
this.appendChild(style);
this.appendChild(wrapper);
}
}
// 注册自定义元素
customElements.define('my-element', MyElement);
</script>
类型扩展创建 HTMLDivElement - Web API | MDN
xml
<div is="expanding-list">
<div>我是注册自定义元素</div>
</div>
<script>
class ExpandingList extends HTMLDivElement {
constructor() {
super()
}
connectedCallback() {
const divs = Array.from(this.querySelectorAll('div'))
setInterval(()=>{
divs.forEach((div) => {
div.style.display = 'none'
})
}, 1000)
setInterval(()=>{
divs.forEach((div) => {
div.style.display = 'block'
})
}, 2000)
}
}
customElements.define('expanding-list', ExpandingList, {extends: 'div'})
</script>
Shadow DOM (隐式 DOM)
kotlin
class MyComponent extends HTMLElement {
constructor() {
super();
// closed | open
const shadowRoot = this.attachShadow({ mode: 'closed' })
const template = document.querySelector('#myTemplate')
const content = template.content.cloneNode(true)
const title = content.querySelector('#title')
const propTitle = this.getAttribute('title')
title.innerText = propTitle
content.querySelector('img').setAttribute('src', this.getAttribute('image'));
content.querySelector('.name').innerText = this.getAttribute('name');
shadowRoot.appendChild(content)
}
}
生命周期
connectedCallback:当自定义元素第一次被连接到文档 DOM 时被调用。
disconnectedCallback:当自定义元素与文档 DOM 断开连接时被调用。
adoptedCallback:当自定义元素被移动到新文档时被调用。
attributeChangedCallback:当自定义元素的一个属性被增加、移除或更改时被调用。
Slot 插槽
xml
<template id="myTemplate">
<style>
h1 {
color: red
}
</style>
<h1 id="title">这是一个大标题</h1>
<slot></slot>
<slot name="slot1"></slot>
<slot name="slot2"></slot>
</template>
<my-component id="myComponent" title="这是传入的标题">
<h2>插槽slot</h2>
<h2 slot="slot1">插槽slot1</h2>
<h2 slot="slot2">插槽slot2</h2>
</my-component>
<script>
class MyComponent extends HTMLElement {
constructor() {
super();
const template = document.querySelector('#myTemplate')
const content = template.content.cloneNode(true)
const title = content.querySelector('#title')
// title.addEventListener('click', ()=>{
// console.log('内部添加click')
// })
this.$title = title
const propTitle = this.getAttribute('title')
title.innerText = propTitle
const shadowRoot = this.attachShadow({ mode: 'open' })
const myEvent = new CustomEvent('myEvent', {
detail: '这是子组件传过来的消息'
})
title.addEventListener('click', () => {
this.dispatchEvent(myEvent)
})
shadowRoot.appendChild(content)
}
static get observedAttributes() {
return ['title']
}
attributeChangedCallback() {
const propTitle = this.getAttribute('title')
this.$title.innerText = propTitle
}
}
document.querySelector('#myComponent').addEventListener('click', e => {
e.target.setAttribute('title', '传入的标题被修改了')
})
window.customElements.define('my-component', MyComponent)
</script>
自定义事件
kotlin
<my-component/>
<script>
// 定义子组件
class MyComponent extends HTMLElement {
static get observedAttributes(){
return ['count']
}
get count(){
return this.getAttribute('count') ? this.getAttribute('count'):0
}
set count(val){
this.setAttribute('count',val)
this.render();
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' })
shadowRoot.innerHTML = `
<style>
h1 {
color: red;
cursor: pointer; /* 指示可点击 */
}
h2 {
color: blue;
cursor: pointer; /* 指示可点击 */
}
</style>
<h1 id="title">这是一个大标题</h1>
<h2 id="title1">这是一个大标题1</h2>
<button id="btnAdd">+</button>
<h3 id="counter">${this.count}</h3>
<button id="btnReduce">-</button>
`
const btn1 = this.shadowRoot.querySelector('#btnAdd')
btn1.addEventListener('click', () => this.count++)
const btn2 = shadowRoot.querySelector('#btnReduce')
btn2.addEventListener('click', () => this.count--)
// 获取模板内容并克隆
// const template = document.getElementById('myTemplate');
// const content = template.content.cloneNode(true);
// 获取 h1 和 h2 元素
const title = this.shadowRoot.querySelector('#title');
const title1 = this.shadowRoot.querySelector('#title1');
// 添加点击事件,触发自定义事件
title.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('h1-click', { detail: 'h1 被点击' }));
});
title1.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('h2-click', { detail: 'h2 被点击' }));
});
}
render() {
this.shadowRoot.querySelector('#counter').textContent = this.count;
}
}
// 注册自定义组件
window.customElements.define('my-component', MyComponent);
window.onload = function() {
const myComponent = document.querySelector('my-component');
// 监听 h1 点击事件
myComponent.addEventListener('h1-click', (event) => {
console.log('接收到的消息:', event.detail); // 输出子组件传递的信息
});
// 监听 h2 点击事件
myComponent.addEventListener('h2-click', (event) => {
console.log('接收到的消息:', event.detail); // 输出子组件传递的信息
});
};
</script>
组件通信
kotlin
<parent-component/>
<script>
class ParentComponent extends HTMLElement {
static get observedAttributes(){
return ['childVal']
}
constructor() {
super();
this.attachShadow({mode:"open"})
this.message = 'Hello World!';
}
get childVal(){
return this.getAttribute('childval'); // 直接从属性中获取值
}
set childVal(value){
this.setAttribute('childval', value);
this.render();
}
connectedCallback() {
this.render();
this.addEventListener('messageToParent', this.handleMessageFromChild.bind(this));
}
render() {
this.shadowRoot.innerHTML = `
<div>
<h1>Parent Component</h1>
<div id='childId'>${this.childVal || ''}</div>
<child-component message="${this.message}"></child-component>
</div>
`;
}
attributeChangedCallback(attr, oldValue, newValue){
onsole.log('attr, oldValue, newValue', attr, oldValue, newValue)
if(attr == 'childVal'){
this.render();
}
}
handleMessageFromChild(event) {
console.log('Received message from child:', event.detail.message);
this.childVal = event.detail.message
// document.getElementById('childId').textContent = event.detail.message
// 在这里处理来自子组件的消息,例如更新父组件的状态
}
}
customElements.define('parent-component', ParentComponent);
class ChildComponent extends HTMLElement {
static get observedAttributes() {
return ['message'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
const button = this.shadowRoot.querySelector('#btn')
button.addEventListener('click', this.sendMessageToParent.bind(this));
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render();
}
}
render() {
this.shadowRoot.innerHTML = `
<div>
<h2>Child Component</h2>
<button id="btn">Send Message to Parent</button>
<p>${this.getAttribute('message')}</p>
</div>
`;
}
sendMessageToParent() {
const customEvent = new CustomEvent('messageToParent', {
detail: { message: 'Hello from Child!' },
bubbles: true,
composed: true // 允许事件穿透 Shadow DOM 边界
});
this.dispatchEvent(customEvent);
}
}
customElements.define('child-component', ChildComponent);
</script>
框架
- X-Tag: 是微软推出的开源库,支持 Web Components 规范,兼容Web Components API。
- Omi: 是 Web Components + JSX/TSX 融合为一个框架,小巧的尺寸和高性能,融合和 React 和 Web Components 各自的优势。
- Slim.js: 是一个开源的轻量级 Web Components 库,它为组件提供数据绑定和扩展能力,使用 es6 原生类继承。专注于帮助开发者更好的编写原生web组件,而不依赖于其他框架,但是也提供了良好的拓展性,开发者可以自由拓展。
- direflow: 是一个 React组件 + web component +web componen t属性变化重新挂载 React 组件的 web component框架。