Переключить только меню, выбранное в Reactjs - PullRequest
3 голосов
/ 09 марта 2020

Я создаю меню и подменю, используя 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

.

Аналогично, он вложен, поэтому после щелчка по любому меню / потомкам их соответствующие следующий уровень должен быть открыт.

Я новичок в способах реагирования и реагирования, так что любая помощь от экспертизы будет полезна для меня, чтобы продолжить и узнать, как на самом деле это нужно делать.

Ответы [ 2 ]

0 голосов
/ 09 марта 2020

Вместо того, чтобы использовать один большой компонент, рассмотрите возможность разделения вашего компонента на один меньший. Таким образом, вы можете добавлять состояние к каждому элементу меню, чтобы переключать нижележащие пункты меню.

Если вы хотите сбросить все нижележащие пункты меню в их закрытое положение по умолчанию, вы должны создавать новый экземпляр компонента каждый раз, когда открываете основные кнопки. При <MenuItemContainer key={timesOpened} MenuItemContainer будет назначен новый ключ, когда вы "откроете" MenuItem. Назначение нового ключа создаст новый экземпляр компонента, а не обновит существующий.

Для подробного объяснения я предлагаю прочитать Возможно, вам не нужно производное состояние - Рекомендация: Полностью неконтролируемый компонент с 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>
0 голосов
/ 09 марта 2020

Вы захотите создать внутренний компонент для управления состоянием на каждом уровне.

Например, рассмотрите следующий функциональный компонент (я оставлю вам преобразование в компонент класса):

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> разделов, и он будет управлять открытым состоянием для каждого уровня.

Поддерживайте общее состояние наверху, но если вам не нужно знать, что-то расширен для других логи c, сохраняйте его локализованным.

Кроме того, если вам нужна эта информация в родительском компоненте, используйте предопределенный объект, который у вас уже есть, и добавьте в него поле «open», по умолчанию ложный. После нажатия на этот объект установите SetState, чтобы правильно пометить соответствующий объект, чтобы при открытии имел значение 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;

...