React项目中如何为数组渲染的嵌套菜单项添加onClick点击展开功能
React多级菜单点击展开实现方案
实现思路
- 为每一级菜单项生成全局唯一标识,避免不同层级id重复导致状态冲突
- 在state中新增
expandedKeys对象存储所有已展开菜单项的标识 - 封装递归渲染方法处理任意层级的菜单结构
- 点击菜单项时切换当前项的展开状态,同步更新箭头方向和子项显隐
核心修改说明
- 首先在state中新增展开状态存储字段
this.state = { // 原有菜单数据不变 expandedKeys: {} // 存储已展开菜单项的唯一key }
- 新增切换展开状态的方法
// 切换菜单展开状态 toggleExpand = (uniqueKey) => { this.setState(prev => ({ expandedKeys: { ...prev.expandedKeys, [uniqueKey]: !prev.expandedKeys[uniqueKey] } })) }
- 封装递归渲染菜单的方法
// 递归渲染菜单项 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> ) }) }
- 修改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
相关产品推荐
相关产品推荐

