React递归函数实现动态菜单:为末级菜单添加Checkbox
解决方案:React递归菜单实现末级Checkbox与独立折叠
我帮你搞定这个递归菜单的需求啦!核心的末级节点加Checkbox,还有可选的独立折叠功能都给你实现好了,一起来看:
一、核心需求:识别末级节点并添加Checkbox
首先,我们得明确末级节点的判断规则:节点没有children属性,或者children数组为空。基于这个逻辑,我们在递归渲染菜单时,给符合条件的节点添加上对应item.id的Checkbox。
修改后的完整代码
const menuData = [ { id: "1", name: "One", children: [ { id: "1.1", name: "One - one", children: [ { id: "1.1.1", name: "One - one - one" }, { id: "1.1.2", name: "One - one - two" }, { id: "1.1.3", name: "One - one - three" } ] } ] }, { id: "2", name: "Two", children: [{ id: "2.1", name: "Two - one" }] }, { id: "3", name: "Three", children: [ { id: "3.1", name: "Three - one", children: [ { id: "3.1.1", name: "Three - one - one", children: [ { id: "3.1.1.1", name: "Three - one - one - one", children: [ { id: "3.1.1.1.1", name: "Three - one - one - one - one" } ] } ] } ] } ] }, { id: "4", name: "Four" }, { id: "5", name: "Five", children: [ { id: "5.1", name: "Five - one" }, { id: "5.2", name: "Five - two" }, { id: "5.3", name: "Five - three" }, { id: "5.4", name: "Five - four" } ] }, { id: "6", name: "Six" } ]; // 基础样式,让菜单层级更清晰 const menuStyles = { ul: { paddingLeft: "20px", margin: "5px 0" }, li: { listStyle: "none", margin: "3px 0" }, checkboxItem: { display: "flex", alignItems: "center", gap: "8px" } }; class App extends React.Component { constructor(props) { super(props); this.state = { currentSelection: "", menuItems: [] }; } componentDidMount() { this.setState({ menuItems: menuData }); } handleClick(id, evt) { evt.preventDefault(); console.log("click handler called with", id); this.setState({ currentSelection: id }); } // 递归构建菜单 buildMenu(items) { return ( <ul style={menuStyles.ul}> {items && items.map(item => ( <li key={item.id} style={menuStyles.li}> {/* 判断是否为末级节点 */} {!item.children || item.children.length === 0 ? ( <div style={menuStyles.checkboxItem}> <input type="checkbox" value={item.id} onChange={() => console.log(`选中节点: ${item.id}`)} /> <span>{item.name}</span> </div> ) : ( <div> <span>{item.name}</span> {/* 递归渲染子菜单 */} {this.buildMenu(item.children)} </div> )} </li> ))} </ul> ); } render() { return ( <div style={{ padding: "20px" }}> <h2>Click any of the below option</h2> {this.state.menuItems && this.state.menuItems.map((item) => { return ( <div key={item.id} style={{ margin: "10px 0" }}> {!item.children || item.children.length === 0 ? ( <div style={menuStyles.checkboxItem}> <input type="checkbox" value={item.id} onChange={() => console.log(`选中节点: ${item.id}`)} /> <span>{item.name}</span> </div> ) : ( <div> <span>{item.name}</span> {this.buildMenu(item.children)} </div> )} </div> ); })} </div> ); } } ReactDOM.render(<App />, document.getElementById("root"));
关键修改说明
- 末级节点判断:通过
!item.children || item.children.length === 0精准识别末级节点 - Checkbox渲染:给末级节点添加inline的Checkbox,value绑定
item.id,还加了简单的选中反馈逻辑 - 样式优化:添加了基础的内联样式,让菜单层级结构更直观,Checkbox和菜单文本对齐更美观
二、可选需求:实现各层级独立折叠
原来的全局折叠体验不太好,我们可以给每个节点维护自己的展开/折叠状态。这里用state中的expandedItems对象来存储每个节点的展开状态(key为节点id,value为布尔值),实现层级独立控制。
修改后的完整代码(含独立折叠)
const menuData = [ { id: "1", name: "One", children: [ { id: "1.1", name: "One - one", children: [ { id: "1.1.1", name: "One - one - one" }, { id: "1.1.2", name: "One - one - two" }, { id: "1.1.3", name: "One - one - three" } ] } ] }, { id: "2", name: "Two", children: [{ id: "2.1", name: "Two - one" }] }, { id: "3", name: "Three", children: [ { id: "3.1", name: "Three - one", children: [ { id: "3.1.1", name: "Three - one - one", children: [ { id: "3.1.1.1", name: "Three - one - one - one", children: [ { id: "3.1.1.1.1", name: "Three - one - one - one - one" } ] } ] } ] } ] }, { id: "4", name: "Four" }, { id: "5", name: "Five", children: [ { id: "5.1", name: "Five - one" }, { id: "5.2", name: "Five - two" }, { id: "5.3", name: "Five - three" }, { id: "5.4", name: "Five - four" } ] }, { id: "6", name: "Six" } ]; // 基础样式,让菜单层级更清晰 const menuStyles = { ul: { paddingLeft: "20px", margin: "5px 0" }, li: { listStyle: "none", margin: "3px 0" }, checkboxItem: { display: "flex", alignItems: "center", gap: "8px" }, toggleBtn: { background: "none", border: "none", cursor: "pointer", marginRight: "8px", fontSize: "14px" } }; class App extends React.Component { constructor(props) { super(props); this.state = { currentSelection: "", menuItems: [], expandedItems: {} // 存储每个节点的展开状态 }; } componentDidMount() { this.setState({ menuItems: menuData }); } handleClick(id, evt) { evt.preventDefault(); console.log("click handler called with", id); this.setState({ currentSelection: id }); } // 切换单个节点的展开/折叠状态 toggleItem = (itemId) => { this.setState(prevState => ({ expandedItems: { ...prevState.expandedItems, [itemId]: !prevState.expandedItems[itemId] } })); }; // 递归构建菜单 buildMenu(items) { return ( <ul style={menuStyles.ul}> {items && items.map(item => ( <li key={item.id} style={menuStyles.li}> {/* 判断是否为末级节点 */} {!item.children || item.children.length === 0 ? ( <div style={menuStyles.checkboxItem}> <input type="checkbox" value={item.id} onChange={() => console.log(`选中节点: ${item.id}`)} /> <span>{item.name}</span> </div> ) : ( <div> <button style={menuStyles.toggleBtn} onClick={() => this.toggleItem(item.id)} > {/* 根据展开状态显示不同符号 */} {this.state.expandedItems[item.id] ? "▼" : "▶"} </button> <span>{item.name}</span> {/* 根据展开状态决定是否渲染子菜单 */} {this.state.expandedItems[item.id] && this.buildMenu(item.children)} </div> )} </li> ))} </ul> ); } render() { return ( <div style={{ padding: "20px" }}> <h2>Click any of the below option</h2> {this.state.menuItems && this.state.menuItems.map((item) => { return ( <div key={item.id} style={{ margin: "10px 0" }}> {!item.children || item.children.length === 0 ? ( <div style={menuStyles.checkboxItem}> <input type="checkbox" value={item.id} onChange={() => console.log(`选中节点: ${item.id}`)} /> <span>{item.name}</span> </div> ) : ( <div> <button style={menuStyles.toggleBtn} onClick={() => this.toggleItem(item.id)} > {this.state.expandedItems[item.id] ? "▼" : "▶"} </button> <span>{item.name}</span> {this.state.expandedItems[item.id] && this.buildMenu(item.children)} </div> )} </div> ); })} </div> ); } } ReactDOM.render(<App />, document.getElementById("root"));
关键修改说明
- 状态管理:新增
expandedItems对象,用来记录每个节点的展开/折叠状态 - 折叠切换方法:
toggleItem方法通过修改expandedItems中对应节点的布尔值,实现独立控制 - 折叠按钮:给有子菜单的节点添加折叠按钮,根据当前状态显示不同的展开/折叠符号
- 条件渲染子菜单:只有当节点处于展开状态时,才渲染对应的子菜单
内容的提问来源于stack exchange,提问作者user12856847
相关产品推荐
相关产品推荐

