[React]利用Webcomponent封装React组件
为什么这么做
我个人认为,最重要的点是可以很方便地跨框架挂载和卸载wc元素(至少我在项目里是这么玩的),此外,基于wc的css沙箱以及它的shadowRoot机制,可以提供一套隔离机制,保证每个渲染组件的边界分明。
利用AI总结罗列了一下都有啥优点…
- 封装性:Web Components提供了一种封装UI组件的方法,使得组件可以在不同的框架或无框架环境中重用。
- 可重用性:封装为Web Components的React组件可以在任何支持Web Components的环境中使用,不限于React应用。
- 封装的样式和行为:Web Components允许你封装组件的HTML结构、样式和行为,确保样式和行为不会泄露到父组件或全局作用域。
- 独立性:Web Components封装的组件具有独立性,它们拥有自己的DOM树和作用域,不会影响外部环境。
- 易于集成:Web Components提供了一种标准化的集成方式,可以更容易地将React组件集成到其他Web应用中。
- 更好的性能:Web Components的自定义元素可以在不影响主线程的情况下进行升级和渲染,这有助于提高应用性能。
- 标准化:Web Components基于W3C标准,这意味着它们在不同的浏览器和环境中具有更好的一致性和兼容性。
- 易于维护:由于Web Components封装的组件具有清晰的接口和封装性,维护和更新组件变得更加容易。
- 样式隔离:Web Components的Shadow DOM技术可以确保组件的样式不会受到外部样式的影响,同时也防止组件内部样式泄露到外部。
- 生命周期管理:Web Components允许你定义组件的生命周期钩子,如
connectedCallback
、disconnectedCallback
等,这与React组件的生命周期方法类似。 - 跨框架使用:封装为Web Components的React组件可以被其他前端框架或库使用,例如Vue、Angular或原生JavaScript。
- 自定义元素:Web Components允许开发者定义自定义HTML元素,这些元素可以像标准HTML元素一样使用。
- 易于测试:Web Components的封装性使得测试组件变得更加简单,因为你可以独立于其他组件来测试它们。
- 更好的封装和抽象:Web Components提供了一种封装和抽象UI组件的方式,使得组件的实现细节对使用者是透明的。
Webcomponent入门
先来简单地过一下webcomponent的基础
官方文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components
示例
下面是一个最简单的示例,自定义了一种名为”simple-component“的元素,并且它没有shadowRoot(意味着它并没有与外界隔离样式)。
class SimpleComponent extends HTMLElement {
constructor() {
super();
this.innerHTML = `<p>Hello, World!</p>`;
}
}
customElements.define('simple-component', SimpleComponent);
下面是一个内容更丰富一些的示例,有基础的大概过一眼也知道大概了。
// 1.自定义标签都是用class 的形式去继承
class myDiv extends HTMLElement {
// 监听
static get observedAttributes() {
return ['option']
}
constructor() {
super()
// 这样我们才能够去追加元素
this.attachShadow({
mode: 'open' })
}
// 重要:生命周期方法 开始
connectedCallback() {
console.log('connectedCallback生命周期')
this.render({
option: this.getAttribute('option'),
})
// 获取元素
console.log(this.shadowRoot.querySelector('.content'))
console.log('this.shadowRoot: ', this.shadowRoot)
document.addEventListener('click', e => {
// 重要:冒泡的顺序,通过这个可以判断有没有在鼠标内部进行点击
if (e.composedPath().includes(this)) {
console.log('点击了里面')
}
})
this.shadowRoot.querySelector('.content').addEventListener('click', e => {
console.log('e: ', e)
// window.dispatchEvent
})
}
// 重要:生命周期方法 重新渲染 .甚至还是第一次进行渲染,比connect还快
// 会重新渲染 connectCallback
attributeChangedCallback(attr, oldValue, newValue) {
if (oldValue) {
switch (attr) {
case 'option':
this.shadowRoot.querySelector('.title').textContent = newValue
}
}
console.log('attributeChangeCallback', attr, oldValue, newValue)
}
borderAdd() {
console.log('borderadd')
this.shadowRoot.querySelector('.content').style.border = '3px solid green'
}
render(data) {
let {
option } = data
// console.log()
let nodeTemplate = document.createElement('template')
nodeTemplate.innerHTML = `
<div class="content" >
<div class="title">${
option} </div>
<slot name="container"></slot>
</div>
`
let nodeStyles = document.createElement('style')
// shadow dom 的样式绝对隔离
// 重要: :host选择器可以选中根也就是my-div的样式。外面的选择器样式要高于这个
nodeStyles.innerHTML = `
:host(.active) .content{
margin-top:20px;
background:rgba(0,0,0,30%);
}
:host{
display:block
}
.content{
width:100px;
height:100px;
background:rgba(0,0,0,20%)
}
::slotted([slot="container"]){
display:none
}
::slotted(.active){
display:block
}
`
this.shadowRoot.appendChild(nodeTemplate.content)
this.shadowRoot.appendChild(nodeStyles)
setTimeout(() => {
this.borderAdd()
}, 3000)
}
}
// 名字必须小写 驼峰必须要转成横线
customElements.define('my-div', myDiv)
shadowRoot
一个Web组件可以有且仅有一个shadowRoot
。shadowRoot
是与该组件关联的影子DOM的根节点。当使用attachShadow
方法创建影子DOM时,它会返回一个shadowRoot
对象,这个对象是唯一的,并且与创建它的元素关联。
例如:
class MyComponent extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({
<