You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Stencil组件自嵌套实现动态手风琴,事件监听触发两次求指导

Stencil动态手风琴事件重复触发问题及优化实现

问题原因

你遇到的事件触发两次的核心问题是事件冒泡:内层自嵌套的子组件触发事件后,事件会沿着DOM树向上传递,外层的同类型组件的事件监听器也会捕获到该事件,从而导致重复触发。

自嵌套场景的临时解决方法

如果一定要保留自嵌套的实现方式,可通过阻止事件冒泡来解决:

  • 在事件触发时调用stopPropagation():
// 子组件内的事件触发函数
triggerEvent = () => {
  const event = new CustomEvent('item-toggle', { detail: { id: this.id } });
  event.stopPropagation(); // 阻止事件向上冒泡
  this.el.dispatchEvent(event);
};
  • 或者在监听事件时处理:
// 父组件(同组件)内的监听逻辑
<my-accordion-item onItemToggle={(e) => {
  e.stopPropagation();
  // 你的业务逻辑
}}>

更优的Stencil动态手风琴实现方案

推荐采用容器组件+子项组件的拆分结构,避免自嵌套带来的事件干扰和状态管理混乱,逻辑更清晰易维护:

1. 容器组件:my-accordion

import { Component, h, Prop, State } from '@stencil/core';

@Component({
  tag: 'my-accordion',
  styleUrl: 'accordion.css',
  shadow: true,
})
export class Accordion {
  // 控制是否允许同时展开多个项
  @Prop() singleOpen: boolean = false;
  // 存储已展开项的ID列表
  @State() expandedIds: string[] = [];

  handleItemToggle = (event: CustomEvent<{ id: string }>) => {
    const targetId = event.detail.id;
    let updatedExpandedIds = [...this.expandedIds];

    if (updatedExpandedIds.includes(targetId)) {
      // 收起当前项
      updatedExpandedIds = updatedExpandedIds.filter(id => id !== targetId);
    } else {
      // 展开当前项,若开启singleOpen则关闭其他项
      updatedExpandedIds = this.singleOpen ? [targetId] : [...updatedExpandedIds, targetId];
    }

    this.expandedIds = updatedExpandedIds;
  };

  render() {
    return (
      <div class="accordion-container">
        <slot onAccordionItemToggle={this.handleItemToggle} />
      </div>
    );
  }
}

2. 子项组件:my-accordion-item

import { Component, h, Prop, State, Event, EventEmitter, Element } from '@stencil/core';

@Component({
  tag: 'my-accordion-item',
  styleUrl: 'accordion-item.css',
  shadow: true,
})
export class AccordionItem {
  @Element() el: HTMLElement;
  @Prop() id: string;
  @Prop() initialExpanded: boolean = false;
  @State() isExpanded: boolean;

  // 向外触发的切换事件
  @Event() accordionItemToggle: EventEmitter<{ id: string }>;

  componentWillLoad() {
    this.isExpanded = this.initialExpanded;
  }

  toggleItem = () => {
    this.isExpanded = !this.isExpanded;
    this.accordionItemToggle.emit({ id: this.id });
  };

  render() {
    return (
      <div class="accordion-item">
        <div 
          class={`accordion-header ${this.isExpanded ? 'expanded' : ''}`}
          onClick={this.toggleItem}
        >
          <slot name="header"></slot>
          <span class="toggle-icon">{this.isExpanded ? '−' : '+'}</span>
        </div>
        {this.isExpanded && (
          <div class="accordion-content">
            <slot name="content"></slot>
          </div>
        )}
      </div>
    );
  }
}

3. 样式文件

accordion.css

.accordion-container {
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  overflow: hidden;
}

accordion-item.css

.accordion-item {
  border-bottom: 1px solid #e5e7eb;
}
.accordion-item:last-child {
  border-bottom: none;
}
.accordion-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 1.25rem;
  background-color: #f9fafb;
  cursor: pointer;
  transition: background-color 0.2s ease;
}
.accordion-header.expanded {
  background-color: #f3f4f6;
}
.accordion-content {
  padding: 1rem 1.25rem;
  background-color: #ffffff;
}
.toggle-icon {
  font-size: 1.25rem;
  font-weight: 600;
  color: #6b7280;
}

4. 使用示例

<my-accordion singleOpen={true}>
  <my-accordion-item id="item-1" initialExpanded={true}>
    <div slot="header">基础设置</div>
    <div slot="content">这里可以放置基础设置的相关内容,比如账号信息、语言偏好等。</div>
  </my-accordion-item>
  <my-accordion-item id="item-2">
    <div slot="header">通知设置</div>
    <div slot="content">配置各类通知的接收方式,包括邮件、短信、站内信等。</div>
  </my-accordion-item>
  <my-accordion-item id="item-3">
    <div slot="header">隐私设置</div>
    <div slot="content">管理个人数据的可见范围,控制第三方应用的权限访问。</div>
  </my-accordion-item>
</my-accordion>

这种拆分方式将状态管理集中在容器组件,子项仅负责自身的UI渲染和事件触发,避免了自嵌套带来的事件冒泡问题,同时扩展性更强,支持多级别嵌套、自定义样式等需求。

内容的提问来源于stack exchange,提问作者surya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 03:07:09