仅切换在 Reactjs 中单击的菜单

Toggle only the menu clicked in Reactjs

我正在使用 recursion function 制作菜单和子菜单,我需要帮助才能打开相应的菜单和子菜单..

用于按钮和折叠 Reactstrap 已被使用..

做菜单填充的递归函数:

{this.state.menuItems &&
          this.state.menuItems.map((item, index) => {
            return (
              <div key={item.id}>
                <Button onClick={this.toggle.bind(this)}> {item.name} </Button>
                <Collapse isOpen={this.state.isToggleOpen}>
                  {this.buildMenu(item.children)}
                </Collapse>
              </div>
            );
          })}

buildMenu函数如下,

  buildMenu(items) {
    return (
      <ul>
        {items &&
          items.map(item => (
            <li key={item.id}>
              <div>
                {this.state.isToggleOpen}
                <Button onClick={this.toggle.bind(this)}> {item.name} </Button>
                <Collapse isOpen={this.state.isToggleOpen}>
                  {item.children && item.children.length > 0
                    ? this.buildMenu(item.children)
                    : null}
                </Collapse>
              </div>
            </li>
          ))}
      </ul>
    );
  }

目前代码没有问题,但我需要帮助 menu -> submenu -> submenu 逐步打开和关闭相应级别。

工作示例:https://codesandbox.io/s/reactstrap-accordion-9epsp

你可以看看这个例子,当你点击任何菜单时,整个级别的菜单都会打开,而不是点击一个。

要求

如果用户单击菜单 One,则子菜单(子)

-> One-One 

需要打开。

然后如果用户点击 One-One

 ->   One-One-One
 ->   One - one - two
 ->   One - one - three

需要打开。

同样,它是嵌套的,因此在单击任何菜单/子项后,需要打开它们各自的下一级。

我是 React 和 Reactstrap 设计方式的新手,所以来自专业知识的任何帮助对我来说都是有用的,可以帮助我继续并了解实际需要如何完成。

您需要创建一个内部组件来管理每个级别的状态。

例如,考虑以下功能组件(我将把它留给您转换为 class 组件):

const MenuButton = ({ name, children }) => {
  const [open, setOpen] = useState(false);
  const toggle = useCallback(() => setOpen(o => !o), [setOpen]);
  return (
    <>
      <Button onClick={toggle}>{name}</Button>
      <Collapse open={open}>{children}</Collapse>
    </>
  );
};

该组件将管理是否显示其子组件。用它代替所有 <div><Button/><Collapse/></div> 部分,它将管理每个级别的打开状态。

将共享状态保持在顶部,但如果您不需要知道某些内容是否针对其他逻辑进行了扩展,请将其保持在本地。

此外,如果您确实需要父组件中的信息,请使用您已有的预定义对象并向其添加默认为 false 的 'open' 字段。单击后,对该对象设置状态以正确标记适当的对象以在打开时具有 true 参数。

虽然本地化状态更清晰。

扩展示例

import React, { Component, useState, useCallback, Fragment } from "react";
import { Collapse, Button } from "reactstrap";
import { loadMenu } from "./service";


const MenuButton = ({ name, children }) => {
  const [open, setOpen] = React.useState(false);
  const toggle = useCallback(() => setOpen(o => !o), [setOpen]);
  return (
    <Fragment>
      <Button onClick={toggle}>{name}</Button>
      <Collapse open={open}>{children}</Collapse>
    </Fragment>
  );
};

class Hello extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentSelection: "",
      menuItems: [],
    };
  }

  componentDidMount() {
    loadMenu().then(items => this.setState({ menuItems: items }));
  }

  buildMenu(items) {
    return (
      <ul>
        {items &&
          items.map(item => (
            <li key={item.id}>
              <MenuButton name={item.name}>
              {item.children && item.children.length > 0
                    ? this.buildMenu(item.children)
                    : null}
              </MenuButton>
            </li>
          ))}
      </ul>
    );
  }

  render() {
    return (
      <div>
        <h2>Click any of the below option</h2>
        {this.state.menuItems &&
          this.state.menuItems.map((item, index) => {
            return (
              <MenuButton name={item.name}>
                {this.buildMenu(item.children)}
              </MenuButton>
            );
          })}
      </div>
    );
  }
}

export default Hello;

与其使用一个大组件,不如考虑将您的组件拆分成更小的组件。这样您就可以为每个菜单项添加状态以切换底层菜单项。

如果您想将所有底层菜单项重置为其默认关闭位置,您应该在每次打开底层按钮时创建一个新的组件实例。通过让 <MenuItemContainer key={timesOpened}MenuItemContainer 将在您 "open" MenuItem 时分配一个新密钥。分配一个新键将创建一个新的组件实例,而不是更新现有的实例。

有关详细说明,我建议阅读 You Probably Don't Need Derived State - Recommendation: Fully uncontrolled component with a key

const loadMenu = () => Promise.resolve([{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 {Component, Fragment} = React;
const {Button, Collapse} = Reactstrap;

class Menu extends Component {
  constructor(props) {
    super(props);
    this.state = {menuItems: []};
  }

  render() {
    const {menuItems} = this.state;
    return <MenuItemContainer menuItems={menuItems} />;
  }

  componentDidMount() {
    loadMenu().then(menuItems => this.setState({menuItems}));
  }
}

class MenuItemContainer extends Component {
  render() {
    const {menuItems} = this.props;
    if (!menuItems.length) return null;
    return <ul>{menuItems.map(this.renderMenuItem)}</ul>;
  }
  
  renderMenuItem(menuItem) {
    const {id} = menuItem;
    return <li key={id}><MenuItem {...menuItem} /></li>;
  }
}
MenuItemContainer.defaultProps = {menuItems: []};

class MenuItem extends Component {
  constructor(props) {
    super(props);
    this.state = {isOpen: false, timesOpened: 0};
    this.open = this.open.bind(this);
    this.close = this.close.bind(this);
  }

  render() {
    const {name, children} = this.props;
    const {isOpen, timesOpened} = this.state;
    return (
      <Fragment>
        <Button onClick={isOpen ? this.close : this.open}>{name}</Button>
        <Collapse isOpen={isOpen}>
          <MenuItemContainer key={timesOpened} menuItems={children} />
        </Collapse>
      </Fragment>
    );
  }

  open() {
    this.setState(({timesOpened}) => ({
      isOpen: true,
      timesOpened: timesOpened + 1,
    }));
  }
  
  close() {
    this.setState({isOpen: false});
  }
}

ReactDOM.render(<Menu />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>

<div id="root"></div>