能否不使用<template>与插槽为自定义元素添加子元素?
实现无需手动写模板/插槽的可容纳子元素的自定义Web组件
你之前的代码拿不到子元素是因为:自定义元素的子元素属于Light DOM(也就是<side-menu>标签包裹的内容),而你在connectedCallback里查询的是Shadow DOM内部.menu的子元素——这是两个完全独立的DOM树,所以返回空NodeList是正常的。
要让自定义元素像<ul>容纳<li>那样直接接受子元素,最标准且简洁的方式是使用Web组件的默认插槽,不需要用户在使用组件时额外写<template>或插槽模板,只需要在组件内部的Shadow DOM中添加<slot>标签即可,它会自动把Light DOM的子元素插入到对应位置。
修改后的完整代码
JavaScript部分
class SideMenu extends HTMLElement { constructor() { super(); this.shadow = this.attachShadow({mode:'open'}); } render() { this.shadow.innerHTML = ` <style> .menu { display: flex; flex-direction: column; height: 100vh; padding: 2em; width: 33vw; gap: 10px; background-color: #2C2B2B; } /* 给插槽内的子元素设置样式,使用::slotted()伪元素 */ .menu ::slotted(*) { color: white; padding: 8px; border-radius: 4px; background-color: #3A3939; cursor: pointer; } </style> <div class="menu"> <!-- 默认插槽:自动插入所有Light DOM子元素 --> <slot></slot> </div> `; } connectedCallback() { this.render(); // 获取组件的子元素,直接查询自定义元素本身(Light DOM) const menuItems = this.children; console.log('当前菜单子元素:', menuItems); } } customElements.define('side-menu', SideMenu);
HTML使用方式
和你原来的写法完全一致,不需要额外模板:
<side-menu> <div>Element1</div> <div>Element2</div> <div>Element3</div> <div>ElementN</div> </side-menu>
关键说明
- 默认插槽
<slot>是Web组件规范的原生特性,会自动接收所有未指定插槽名的Light DOM子元素,完全满足你“像ul/li那样直接使用”的需求。 - 由于Shadow DOM的样式隔离特性,要给插槽内的子元素设置样式,必须使用
::slotted()伪元素,它可以选中插槽内的顶层元素。 - 如果需要获取子元素,不要去Shadow DOM里查询,直接通过
this.children或this.querySelectorAll('*')操作自定义元素本身即可,这些是Light DOM中的子元素。
内容的提问来源于stack exchange,提问作者Victor Santizo
相关产品推荐
相关产品推荐

