Web Components 开发中的 Shadow DOM 坑
在当前前端开发技术中,Web Components 已经成为开发高度复用的组件的重要手段。而 Shadow DOM 是其中最重要的一部分,它能使 Web Components 实现真正的封装以及样式隔离。但是,并不是每个人都能够顺利地在开发过程中使用好 Shadow DOM,下面我们将会深入探讨在 Web Components 开发中遇到的 Shadow DOM 坑以及如何避免它们。
一、Shadow DOM 基本概念与实现方法
Shadow DOM 是一个独立于主文档 DOM 树的一组 DOM 树,这个组件内部的样式和脚本代码都只在组件内部生效,不会影响页面的其他部分。其实现方式通常包括两部分:
- 在组件内部创建 Shadow DOM,可以使用 createElement 方法创建元素,在元素上设置 shadowRoot 属性即可创建 Shadow DOM。如下所示:
// www.javascriptcn.com code example
class MyComponent extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' }); // 创建 Shadow DOM
const h1 = document.createElement("h1");
h1.innerHTML = "Hello World";
shadowRoot.appendChild(h1); // 添加 h1 到 Shadow DOM 中
}
}
customElements.define('my-component', MyComponent);- 使用 CSS 规则在 Shadow DOM 中设置样式。Shadow DOM 中的样式不会对页面中其他部分生效。如下所示:
// www.javascriptcn.com code example
<my-component>
#shadow-root
<style>
div {
color: red;
}
</style>
<div>Hello World</div>
</my-component>二、Shadow DOM 坑
- 无法继承父元素样式
因为 Shadow DOM 是独立于主文档 DOM 树的,所以它无法继承主文档中元素的样式,例如:
// www.javascriptcn.com code example
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Shadow DOM 坑</title>
<style>
div {
color: red;
}
</style>
</head>
<body>
<div>我是红色的文字</div>
<my-component></my-component>
<script>
class MyComponent extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "open" });
const div = document.createElement("div");
div.innerHTML = "我是黑色的文字";
shadowRoot.appendChild(div);
}
}
customElements.define("my-component", MyComponent);
</script>
</body>
</html>即使页面中存在样式 div { color: red; },但是在 Shadow DOM 中的 div 元素却不会继承该样式,必须在 Shadow DOM 中重新定义样式。
- 需要使用 /deep/ 或者 ::slotted
如果 Shadow DOM 中嵌套了组件,并且这个组件也使用了 Shadow DOM,则不能直接在这个组件内使用样式。需要使用 /deep/ 或者 ::slotted 来访问 Shadow DOM 中的元素。
// www.javascriptcn.com code example
<my-component>
#shadow-root
<style>
/* 使用 /deep/ */
/deep/ .inner-content {
background: red;
}
</style>
<div class="outer-content">
<my-component>
#shadow-root
<style>
/* 使用 ::slotted */
::slotted(.inner-content) {
background: green;
}
</style>
<div class="inner-content"></div>
</my-component>
</div>
</my-component>- slot 不能嵌套
slot 是自定义元素与其 Shadow DOM 之间的通信机制,允许父组件向子组件插入内容。但是,slot 不允许嵌套。
<my-component>
#shadow-root
<slot>
<div>插入的内容不能再包含 slot 元素</div>
</slot>
</my-component>- 禁止给 Shadow DOM 根节点添加事件
Shadow DOM 根节点与主文档根节点不是同一个节点,因此禁止直接在 Shadow DOM 根节点上添加事件。如果需要添加事件,可以尝试使用宿主元素来代理。
// www.javascriptcn.com code example
class MyComponent extends HTMLElement {
constructor() {
super();
this.addEventListener('click', (event) => {
const shadowRoot = this.shadowRoot;
const target = event.composedPath()[0];
if (target === shadowRoot.querySelector('#btn')) {
alert('Click Button');
}
});
const shadowRoot = this.attachShadow({ mode: 'open' });
const button = document.createElement('button');
button.setAttribute('id', 'btn');
button.innerHTML = 'Click Me';
shadowRoot.appendChild(button);
}
}
customElements.define('my-component', MyComponent);三、如何避免 Shadow DOM 坑
- 使用 ::part 和 ::theme
::part 和 ::theme 是 Shadow DOM 规范中两个比较新的伪类,主要作用是用来给组件的内部元素打标签或者添加 CSS 主题。通过使用 ::part 和 ::theme,可以避免使用 /deep/ 或者 ::slotted 来解决 CSS 样式的问题,使得组件更加清晰、易于维护。
// www.javascriptcn.com code example
<my-component>
#shadow-root
<style>
:part(content) {
color: red;
}
</style>
<div part="content">Hello World</div>
</my-component>- 建议使用 slot 和组件通信
在 Web Components 开发中,建议使用 slot 作为组件通信的主要方式,而不要使用直接在 Shadow DOM 根节点上添加事件的方式。同时,为了避免子组件在 slot 之中使用 slot,可以使用 HTML Template 元素,例如:
// www.javascriptcn.com code example
<my-component>
#shadow-root
<template>
<div slot="content">
<slot name="content"></slot>
</div>
</template>
<slot name="content"></slot>
</my-component>这样,就可以避免 slot 的嵌套问题。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67cf91ece46428fe9eb1ecbc