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

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"));

关键修改说明

  1. 末级节点判断:通过!item.children || item.children.length === 0精准识别末级节点
  2. Checkbox渲染:给末级节点添加inline的Checkbox,value绑定item.id,还加了简单的选中反馈逻辑
  3. 样式优化:添加了基础的内联样式,让菜单层级结构更直观,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"));

关键修改说明

  1. 状态管理:新增expandedItems对象,用来记录每个节点的展开/折叠状态
  2. 折叠切换方法:toggleItem方法通过修改expandedItems中对应节点的布尔值,实现独立控制
  3. 折叠按钮:给有子菜单的节点添加折叠按钮,根据当前状态显示不同的展开/折叠符号
  4. 条件渲染子菜单:只有当节点处于展开状态时,才渲染对应的子菜单

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 21:32:31