Web 组件是现代 Web 开发中不可或缺的一部分,Custom Element 是 Web 组件的基础,在实际应用过程中我们可能需要嵌套多层 Custom Element 来实现复杂的组件结构。但是,Custom Element 的层级嵌套会带来一些问题,例如继承样式的问题、事件冒泡问题等。那么如何解决这些问题呢?下面将从多个方面探讨如何解决 Custom Element 的层级嵌套问题。
继承样式的问题
Custom Element 的层级嵌套可能会导致样式丢失或应用不到嵌套的子元素上。这个问题的原因是当子元素被插入到父元素中时,子元素的 CSS 规则不会与父元素的 CSS 规则合并,而是分开计算,所以子元素的 CSS 规则不会继承父元素的 CSS 规则。解决这个问题的方法是使用 ::part 和 ::theme 伪类。假设有以下的 HTML 结构:
<my-element> <div class="inner">Inner Content</div> </my-element>
我们可以在父级 Custom Element 中使用 ::part 为子元素设置样式:
my-element::part(inner) {
color: red;
}这样,子元素的 inner 类名就绑定到 ::part(inner) 了,子元素就可以应用父元素的样式了。
事件冒泡问题
在 Custom Element 的层级嵌套中,子元素的事件可以向上冒泡,这会带来一些问题。例如有两个 Custom Element,分别叫 parent-element 和 child-element,然后在 child-element 中绑定了一个点击事件,那么当点击 child-element 时,父级 parent-element 和祖先级元素的点击事件都会被触发。
为了解决这个问题,我们可以使用 composed 属性。这个属性允许跨越 Shadow DOM 边界触发事件。如果设置了 composed: true,那么事件会穿过 Shadow DOM 边界,沿着正常的 DOM 树向上传递。例如:
childElement.addEventListener('click', evt => {
evt.stopPropagation();
const event = new CustomEvent('my-event', {
bubbles: true,
composed: true
});
childElement.dispatchEvent(event);
});这样,当点击 child-element 时,只会触发 child-element 的 my-event 事件,不会触发父级和祖先级元素的事件了。
示例代码
// www.javascriptcn.com code example
<!-- parent-element.html -->
<template>
<style>
:host {
display: block;
background-color: #fff;
border: 1px solid #333;
padding: 20px;
}
::part(inner) {
color: red;
}
</style>
Parent Element
<slot></slot>
</template>
<script>
class ParentElement extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const templateEl = document.querySelector('#parent-element-template');
const instance = templateEl.content.cloneNode(true);
shadow.appendChild(instance);
}
}
customElements.define('parent-element', ParentElement);
</script>
<!-- child-element.html -->
<template>
<style>
:host {
display: block;
background-color: #eee;
border: 1px solid #333;
padding: 10px;
}
</style>
Child Element
<button id="button">Click Me</button>
</template>
<script>
class ChildElement extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const templateEl = document.querySelector('#child-element-template');
const instance = templateEl.content.cloneNode(true);
shadow.appendChild(instance);
this.btn = this.shadowRoot.getElementById('button');
this.btn.addEventListener('click', evt => {
evt.stopPropagation();
const event = new CustomEvent('my-event', {
bubbles: true,
composed: true
});
this.dispatchEvent(event);
});
}
}
customElements.define('child-element', ChildElement);
</script>
<!-- index.html -->
<html>
<head>
<script src="parent-element.js"></script>
<script src="child-element.js"></script>
</head>
<body>
<parent-element>
<child-element></child-element>
</parent-element>
<template id="parent-element-template">
<parent-element></parent-element>
</template>
<template id="child-element-template">
<child-element></child-element>
</template>
</body>
</html>在这个代码中,我们假设有两个 Custom Element,分别为 parent-element 和 child-element。在 parent-element 中通过 ::part 伪类为 inner 类名的元素设置了文本颜色。在 child-element 中绑定了一个点击事件,并在点击事件中使用 composed 属性触发了一个 my-event 事件。
同时,在 index.html 中,我们又将 parent-element 和 child-element 分别实例化,并将 child-element 放置在 parent-element 中。
通过这个代码的运行,我们可以看到,在 parent-element 中的 inner 元素的文字颜色被正确继承。在 child-element 中,点击按钮不会触发 parent-element 的点击事件了。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67cfba35e46428fe9eb9bfa2