【问题标题】:Toggle only the menu clicked in Reactjs仅切换 Reactjs 中单击的菜单
【发布时间】:2020-06-21 10:48:51
【问题描述】:

我正在使用 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 -&gt; submenu -&gt; submenu 逐步打开和关闭各个级别。

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

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

要求

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

-> One-One 

需要打开。

然后如果用户点击One-One

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

需要打开。

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

我是 react 和 reactstrap 设计方式的新手,所以任何来自专业人士的帮助都会有助于我继续学习并了解实际需要如何完成。

【问题讨论】:

    标签: javascript reactjs accordion collapse reactstrap


    【解决方案1】:

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

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

    有关详细说明,我建议阅读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>

    【讨论】:

    • 我正在接受它,但我在这里也遇到了一个问题,就像我在其他解决方案中提到的那样。如果我打开到最后一级菜单打开明智,它第一次工作正常。然后如果我在说(第二级)关闭它,如果我再次点击第二级,下一个级别应该只打开,但它会打开到最后一个级别。所以关闭一个级别的菜单并再次打开显示所有级别而不是下一个级别。请查看并更新您的答案
    • @TestUser 我已经根据要求更新了答案。我确实觉得 ref 解决方案不是最好的方法,但是为时已晚,这是最简单的方法。明天我会重新看问题,看看我是否能找到更好的东西。
    • 是的,如果可能的话,请用最好的方法更新解决方案..提前致谢..
    • 你能帮我解决这个问题吗? stackoverflow.com/questions/60616336/…
    • @TestUser 我已将解决方案重构为使用“带有密钥的完全不受控制的组件”解决方案。查看答案中提供的链接。今天晚些时候我会看看这个新问题。
    【解决方案2】:

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

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

    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>
        </>
      );
    };
    
    

    这个组件将管理是否显示它的子组件。使用它代替您的所有 &lt;div&gt;&lt;Button/&gt;&lt;Collapse/&gt;&lt;/div&gt; 部分,它将管理每个级别的打开状态。

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

    另外,如果您在父组件中确实需要该信息,请使用您已有的预定义对象并向其添加默认为 false 的“打开”字段。单击后,在该对象上设置状态以正确标记适当的对象,以便在打开时具有 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;
    
    

    【讨论】:

    • 感谢您的建议.. 请您分叉我的代码沙箱并给我正确的解决方案,这将真正帮助我更好地理解..
    • 我在回答中添加了一个扩展示例。
    • 感谢您的帮助将在我的实际应用程序中实现这一点。但是对于我在代码和框中显示的内容,您已使其正常工作,因此非常感谢您的工作,我将赞成并接受它..因为你在这里帮助了我,我想你也可以在这里解决我的另一个问题stackoverflow.com/questions/60600098/… ..在最后一级菜单中,我需要与最后一级菜单项内联的复选框..
    • 此外,我在您的解决方案中发现了一个缺陷,如果我打开到最后一级菜单打开明智,它第一次工作正常。然后如果我在说(第二级)关闭它,然后再次如果我点击第二个级别,下一个级别应该只打开,但它会打开到最后一个级别。所以关闭一个级别的菜单并再次打开显示所有级别而不是下一个级别。请查看并更新您的回答..
    猜你喜欢
    • 1970-01-01
    • 2012-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-23
    • 1970-01-01
    • 2017-11-28
    相关资源
    最近更新 更多