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

React项目中如何为数组渲染的嵌套菜单项添加onClick点击展开功能

React多级菜单点击展开实现方案

实现思路

  • 为每一级菜单项生成全局唯一标识,避免不同层级id重复导致状态冲突
  • 在state中新增expandedKeys对象存储所有已展开菜单项的标识
  • 封装递归渲染方法处理任意层级的菜单结构
  • 点击菜单项时切换当前项的展开状态,同步更新箭头方向和子项显隐

核心修改说明

  1. 首先在state中新增展开状态存储字段
this.state = {
  // 原有菜单数据不变
  expandedKeys: {} // 存储已展开菜单项的唯一key
}
  1. 新增切换展开状态的方法
// 切换菜单展开状态
toggleExpand = (uniqueKey) => {
  this.setState(prev => ({
    expandedKeys: {
      ...prev.expandedKeys,
      [uniqueKey]: !prev.expandedKeys[uniqueKey]
    }
  }))
}
  1. 封装递归渲染菜单的方法
// 递归渲染菜单项
renderMenuItems = (items, parentKey = '') => {
  return items.map(item => {
    // 生成当前项的唯一key,避免不同层级id重复
    const uniqueKey = parentKey ? `${parentKey}-${item.id}` : `${item.id}`
    const isExpanded = this.state.expandedKeys[uniqueKey]
    const hasChildren = item.items && item.items.length > 0
    return (
      <div key={uniqueKey}>
        <div
          className={styles.button}
          onClick={() => hasChildren && this.toggleExpand(uniqueKey)}
          style={{ paddingLeft: `${parentKey.split('-').length * 16}px` }} // 层级缩进,可根据需求调整数值
        >
          {item.title}
          {hasChildren && (
            <i
              className={`fa ${isExpanded ? 'fa-chevron-down' : 'fa-chevron-right'}`}
              aria-hidden="true"
              style={{ marginLeft: '8px' }}
            />
          )}
        </div>
        <div className={styles.line} />
        {/* 展开且有子项时递归渲染子菜单 */}
        {isExpanded && hasChildren && this.renderMenuItems(item.items, uniqueKey)}
      </div>
    )
  })
}
  1. 修改render方法调用递归渲染函数
render() {
  const { message } = this.state;
  return (
    <div>
      <p className={styles.titleText}>AAAAAA</p>
      <div className={styles.line} />
      {this.renderMenuItems(message)}
    </div>
  );
}

完整修改后代码

import React from 'react';
// 引入自身项目的样式文件即可
// import styles from './Chat.module.css';

export default class Chat extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            message: [
                {
                    id: 1,
                    title: "How will i get cashback?",
                    items: [
                        {
                            id: 1,
                            title: "Page Page One",
                            items: [
                                     {
                                        id: 1,
                                        title: 'SubContent'
                                      }
                                   ],
                        },
                        {
                            id: 2,
                            title: "Two",
                        },
                    ],
                },
                {
                    id: 2,
                    title: "Reschedule delivery",
                    items: [],
                },
            ], 
            expandedKeys: {}
        };
    }

    toggleExpand = (uniqueKey) => {
      this.setState(prev => ({
        expandedKeys: {
          ...prev.expandedKeys,
          [uniqueKey]: !prev.expandedKeys[uniqueKey]
        }
      }))
    }

    renderMenuItems = (items, parentKey = '') => {
      return items.map(item => {
        const uniqueKey = parentKey ? `${parentKey}-${item.id}` : `${item.id}`
        const isExpanded = this.state.expandedKeys[uniqueKey]
        const hasChildren = item.items && item.items.length > 0
        return (
          <div key={uniqueKey}>
            <div
              className={styles.button}
              onClick={() => hasChildren && this.toggleExpand(uniqueKey)}
              style={{ paddingLeft: `${parentKey.split('-').length * 16}px` }}
            >
              {item.title}
              {hasChildren && (
                <i
                  className={`fa ${isExpanded ? 'fa-chevron-down' : 'fa-chevron-right'}`}
                  aria-hidden="true"
                  style={{ marginLeft: '8px' }}
                />
              )}
            </div>
            <div className={styles.line} />
            {isExpanded && hasChildren && this.renderMenuItems(item.items, uniqueKey)}
          </div>
        )
      })
    }

    render() {
        const { message } = this.state;
        return (
                <div>
                        <p className={styles.titleText}>AAAAAA</p>
                        <div className={styles.line} />
                        {this.renderMenuItems(message)}
                    </div>
        );
    }
}

效果说明

  • 点击有子项的菜单项时会自动切换展开/收起状态
  • 箭头会跟随展开状态自动切换方向
  • 不同层级的菜单项会自动缩进,视觉层级清晰
  • 支持任意层级的菜单嵌套,不需要手动修改渲染逻辑
  • 若需要显示中文菜单,直接替换message数组中对应title字段即可:
    • "How will i get cashback?" → 我如何获得返现?
    • "Page Page One" → 第一页
    • "Two" → 第二页
    • "SubContent" → 子内容
    • "Reschedule delivery" → 重新安排配送

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.13 11:12:00