事件委托在树形菜单中实现折叠/展开,核心是绑定click事件到父容器,通过event.target和closest()识别触发节点,切换子容器显示状态并维护统一状态对象,同时过滤无关元素确保可访问性。

事件委托在树形菜单中实现折叠/展开,核心是利用事件冒泡,把点击监听绑定到父容器,再根据实际点击的目标节点类型(如箭头图标、菜单项文字等)统一处理状态切换。
绑定事件到菜单根容器
不要给每个子节点单独加 click 监听器,而是只在最外层的树容器(比如 <ul class="tree"></ul> 或 <div class="tree-root">)上监听一次 <code>click 事件。这样无论后续动态添加多少子节点,都不需要重新绑定。
- 推荐使用
event.target获取真实被点击的元素,而非this - 用
closest()向上查找最近的可交互节点,比如带data-node-id或class="tree-toggle"的按钮 - 避免依赖层级深度,保持结构灵活(支持多级嵌套)
识别可折叠节点并触发状态切换
树形菜单中,通常只有带子节点的父项才需要折叠控制。可通过以下方式判断:
- 检查
event.target是否匹配折叠触发器(例如:svg.icon-caret, .tree-item > .toggle-btn) - 或通过
event.target.closest('.tree-item[data-has-children="true"]')定位父节点 - 获取该节点的唯一标识(如
data-id),再查其子列表(ul.tree-children或div.tree-children) - 切换子容器的
display或增删collapsed类,并更新箭头图标旋转状态
统一维护展开/折叠状态(可选进阶)
如果需要记忆用户操作、支持默认展开某几项,或做联动(如“全部展开”),建议维护一个轻量状态对象:
- 用
Map或普通对象记录{ nodeId: true/false },true表示已展开 - 初始化时遍历 DOM 或读取初始数据,预设默认状态
- 每次点击后更新状态,并同步 DOM(比如设置
aria-expanded、data-expanded) - 不依赖 DOM 属性读取当前状态,避免因手动修改 DOM 导致状态错乱
防止事件误触发与边界处理
树形菜单常含链接、复选框、操作按钮等其他交互元素,需过滤无关点击:
- 用
event.target.matches('button.toggle, .icon-collapse, [data-action="toggle"]')精确匹配目标 - 对
a标签、input、label等显式排除(!event.target.matches('a, input, label')) - 调用
event.stopPropagation()仅在必要时使用(比如内部有自定义事件),多数情况靠选择器过滤更安全 - 确保折叠区域有明确的
role="group"和aria-hidden配合,提升可访问性
不复杂但容易忽略的是:折叠逻辑应与数据模型解耦,DOM 只负责渲染和响应,状态变更由统一函数驱动——这样未来接入 Vue/React 或做服务端渲染时,行为依然一致。











