IDに基づいてデフォルトで折りたたみ可能なメニューを開く


8

ネストされたメニューとサブメニューを作成していますが、すべてが今のところ完了しています。指定されたIDに基づいてデフォルトでこの折りたたみ可能なメニューを開くようにする必要があります。

以下の完全に機能するコードスニペットを確認することもできます。

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 openMenuId = "3.1.1.1";

const {Component, Fragment} = React;
const {Button, Collapse, Input} = Reactstrap;

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

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

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

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

class MenuItem extends Component {
  constructor(props) {
    super(props);
    this.state = {isOpen: false};
    this.toggle = this.toggle.bind(this);
  }

  render() {
    let isLastChild = this.props.children ? false : true;
    return (
      <Fragment>
        <Button onClick={this.toggle}>{this.props.name}</Button>
        <Fragment>
          {isLastChild ? <Input type="checkbox" value={this.props.id} /> : ''}
        </Fragment>
        <Collapse isOpen={this.state.isOpen}>
          <MenuItemContainer menuItems={this.props.children} />
        </Collapse>
      </Fragment>
    );
  }

  toggle() {
    this.setState(({isOpen}) => ({isOpen: !isOpen}));
  }
}

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>

要件:

const openMenuId = "3.1.1.1.1";親コンポーネントにid値を格納しています(この変数はloadMenu配列変数の下にあります)。

複数のサブメニューがある場合でも、このIDは最後のレベルの子IDにのみ属するため、チェックボックスが確実にあるため、チェックボックスをオンにし、親レベルまでのメニューを開く必要があります。

例えば..、

openMenuIdがあるので"3.1.1.1.1"、したがって、メニューの最後の子レベルであることは明らかthreeであるThree - one - one - one - oneようにチェックする必要がありますopenMenuIdと、チェックボックスの値がここで試合を持っている...そして、それぞれのメニューとサブメニューは、最後のレベルまで拡張する必要があります。

これは、アクセスしたページのデフォルトの動作のみであるため、ユーザーは折りたたんで他のメニューの他のチェックボックスをオンにできます。しかし、ページにアクセスしている間、デフォルトで開く必要がある特定のIDがあり、またチェックボックスでチェックする必要があります。

小道具として渡されたIDを比較してそれぞれのメニューをチェックすることで、それぞれのメニューを開いた結果を達成できるようにしてください。

長い間苦労しておりますので、よろしくお願いします。

回答:


3

すばらしい質問です。これに対する解決策を思いつくのは本当に楽しかったです。

メニューの状態とチェックボックスの状態の両方に初期状態を指定したかったので、<Menu>レベル(またはそれ以上)で両方の状態を制御することをお勧めします。これにより、親から初期状態を簡単に定義できるだけでなく、将来さらに複雑なメニューやチェックボックスの動作が必要になった場合に、より柔軟に対応できます。

メニューの構造は再帰的であるため、メニューの状態に再帰的構造を持たせるとかなりうまくいくと思います。コードに入る前に、短いGIFを以下に示します。これは、状態がどのように見えるかを説明するのに役立ちます。

デモ

メニューの3つの列を示すビデオ:メニュー、メニューの状態はJSON、チェックボックスの状態はJSON。 メニューとチェックボックスをクリックすると、状態が更新されます。

これが遊び場のスニペットです:

回答

以下のコードウォークスルー。

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, Input } = Reactstrap;

const replaceNode = (replacer, node, idPath, i) => {
  if (i <= idPath.length && !node) {
    // Not at target node yet, create nodes in between
    node = {};
  }
  if (i > idPath.length) {
    // Reached target node
    return replacer(node);
  }

  // Construct ID that matches this depth - depth meaning
  // the amount of dots in between the ID
  const id = idPath.slice(0, i).join(".");
  return {
    ...node,
    // Recurse
    [id]: replaceNode(replacer, node[id], idPath, i + 1)
  };
};

const replaceNodeById = (node, id, visitor) => {
  // Pass array of the id's parts instead of working on the string
  // directly - easy way to handle multi-number ID parts e.g. 3.1.15.32
  return replaceNode(visitor, node, id.split("."), 1);
};

const expandedNode = () => ({});
const unexpandedNode = () => undefined;

const toggleNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode =>
    oldNode ? unexpandedNode() : expandedNode()
  );
const expandNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode => expandedNode());

class Menu extends Component {
  constructor(props) {
    super(props);
    this.state = {
      menuItems: [],
      openMenus: {},
      checkedMenus: {}
    };
    this.handleMenuToggle = this.handleMenuToggle.bind(this);
    this.handleChecked = this.handleChecked.bind(this);
  }

  render() {
    const { menuItems, openMenus, checkedMenus } = this.state;

    return (
      <MenuItemContainer
        openMenus={openMenus}
        menuItems={menuItems}
        onMenuToggle={this.handleMenuToggle}
        checkedMenus={checkedMenus}
        onChecked={this.handleChecked}
      />
    );
  }

  componentDidMount() {
    const { initialOpenMenuId, initialCheckedMenuIds } = this.props;

    loadMenu().then(menuItems => {
      const initialMenuState = {};
      this.setState({
        menuItems,
        openMenus: expandNodeById(initialMenuState, initialOpenMenuId),
        checkedMenus: initialCheckedMenuIds.reduce(
          (acc, val) => ({ ...acc, [val]: true }),
          {}
        )
      });
    });
  }

  handleMenuToggle(toggledId) {
    this.setState(({ openMenus }) => ({
      openMenus: toggleNodeById(openMenus, toggledId)
    }));
  }

  handleChecked(toggledId) {
    this.setState(({ checkedMenus }) => ({
      checkedMenus: {
        ...checkedMenus,
        [toggledId]: checkedMenus[toggledId] ? unexpandedNode() : expandedNode()
      }
    }));
  }
}

function MenuItemContainer({
  openMenus,
  onMenuToggle,
  checkedMenus,
  onChecked,
  menuItems = []
}) {
  if (!menuItems.length) return null;

  const renderMenuItem = menuItem => (
    <li key={menuItem.id}>
      <MenuItem
        openMenus={openMenus}
        onMenuToggle={onMenuToggle}
        checkedMenus={checkedMenus}
        onChecked={onChecked}
        {...menuItem}
      />
    </li>
  );

  return <ul>{menuItems.map(renderMenuItem)}</ul>;
}

class MenuItem extends Component {
  constructor(props) {
    super(props);
    this.handleToggle = this.handleToggle.bind(this);
    this.handleChecked = this.handleChecked.bind(this);
  }

  render() {
    const {
      children,
      name,
      id,
      openMenus,
      onMenuToggle,
      checkedMenus,
      onChecked
    } = this.props;

    const isLastChild = !children;
    return (
      <Fragment>
        <Button onClick={isLastChild ? this.handleChecked : this.handleToggle}>
          {name}
        </Button>
        {isLastChild && (
          <Input
            addon
            type="checkbox"
            onChange={this.handleChecked}
            checked={!!checkedMenus[id]}
            value={id}
          />
        )}

        <Collapse isOpen={openMenus ? !!openMenus[id] : false}>
          <MenuItemContainer
            menuItems={children}
            // Pass down child menus' state
            openMenus={openMenus && openMenus[id]}
            onMenuToggle={onMenuToggle}
            checkedMenus={checkedMenus}
            onChecked={onChecked}
          />
        </Collapse>
      </Fragment>
    );
  }

  handleToggle() {
    this.props.onMenuToggle(this.props.id);
  }

  handleChecked() {
    this.props.onChecked(this.props.id);
  }
}

ReactDOM.render(
  <Menu initialOpenMenuId="3.1.1.1" initialCheckedMenuIds={["3.1.1.1.1"]} />,
  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>

ウォークスルー

始める前に、私は自由にコードの一部を変更して、オブジェクトの構造化配列の構造化残りデフォルト値などの最新のJavaScript機能を使用するようにしたことを言わなければなりません。

状態を作成する

そう。メニュー項目のIDはドットで区切られた数字であるため、状態を構築するときにこれを利用できます。状態は基本的にツリーのような構造で、各サブメニューはその親の子であり、リーフノード(「最後のメニュー」または「最深のメニュー」)は、{}展開されているかundefinedどうかにかかわらず値を持っています。メニューの初期状態を構築する方法は次のとおりです。

<Menu initialOpenMenuId="3.1.1.1" initialCheckedMenuIds={["3.1.1.1.1"]} />

// ...

loadMenu().then(menuItems => {
  const initialMenuState = {};
  this.setState({
    menuItems,
    openMenus: expandNodeById(initialMenuState, initialOpenMenuId),
    checkedMenus: initialCheckedMenuIds.reduce(
      (acc, val) => ({ ...acc, [val]: true }),
      {}
    )
  });
});

// ...

const expandedNode = () => ({});
const unexpandedNode = () => undefined;

const toggleNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode =>
    oldNode ? unexpandedNode() : expandedNode()
  );
const expandNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode => expandedNode());

const replaceNodeById = (node, id, visitor) => {
  // Pass array of the id's parts instead of working on the string
  // directly - easy way to handle multi-number ID parts e.g. 3.1.15.32
  return replaceNode(visitor, node, id.split("."), 1);
};

const replaceNode = (replacer, node, idPath, i) => {
  if (i <= idPath.length && !node) {
    // Not at target node yet, create nodes in between
    node = {};
  }
  if (i > idPath.length) {
    // Reached target node
    return replacer(node);
  }

  // Construct ID that matches this depth - depth meaning
  // the amount of dots in between the ID
  const id = idPath.slice(0, i).join(".");
  return {
    ...node,
    // Recurse
    [id]: replaceNode(replacer, node[id], idPath, i + 1)
  };
};

これを少しずつ分解してみましょう。

const expandedNode = () => ({});
const unexpandedNode = () => undefined;

これらは定義した便利な関数にすぎないため、展開されたノードと展開されていないノードを表すために使用する値を簡単に変更できます。また、単にリテラルを使用する{}場合やundefinedコード内で使用する場合に比べて、コードが少し読みやすくなります。展開された値と展開されていない値は同じようにtrueなりfalse、展開されたノードは真実であり、展開されていないノードは偽物であることが重要です。これについては後で詳しく説明します。

const toggleNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode =>
    oldNode ? unexpandedNode() : expandedNode()
  );
const expandNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode => expandedNode());

これらの関数を使用して、メニュー状態の特定のメニューを切り替えたり展開したりできます。最初のパラメータはメニューの状態そのもの、2番目はメニューの文字列ID(例:)"3.1.1.1.1"、3番目は置換を行う関数です。これを、渡した関数のように考えてください.map()。置換機能は実際の再帰的なツリー反復から分離されているため、後で簡単に追加の機能を実装できます。たとえば、特定のメニューを展開しない場合は、を返す関数を渡すだけunexpandedNode()です。

const replaceNodeById = (node, id, visitor) => {
  // Pass array of the id's parts instead of working on the string
  // directly - easy way to handle multi-number ID parts e.g. 3.1.15.32
  return replaceNode(visitor, node, id.split("."), 1);
};

この関数は、前の2つの関数で使用され、よりクリーンなインターフェイスを提供します。ここでは、IDがドット(.)で分割されており、ID部分の配列を示しています。次の関数は、ID文字列の代わりにこの配列を直接操作します。これは、そのようにして.indexOf('.')不正操作を行う必要がないためです。

const replaceNode = (replacer, node, idPath, i) => {
  if (i <= idPath.length && !node) {
    // Not at target node yet, create nodes in between
    node = {};
  }
  if (i > idPath.length) {
    // Reached target node
    return replacer(node);
  }

  // Construct ID that matches this depth - depth meaning
  // the amount of dots in between the ID
  const id = idPath.slice(0, i).join(".");
  return {
    ...node,
    // Recurse
    [id]: replaceNode(replacer, node[id], idPath, i + 1)
  };
};

replaceNode機能は、問題の肉です。これは、古いメニューツリーから新しいツリーを生成する再帰的な関数であり、古いターゲットノードを提供されたreplacer関数で置き換えます。ツリーの途中からパーツが欠落している場合(たとえば、ツリーは{}ノード3.1.1.1であるがノードを置き換えたい場合)は、その間に親ノードを作成します。mkdir -pコマンドに慣れているようなものです。

これがメニューの状態です。チェックボックスの状態(checkedMenus)は基本的に単なるインデックスであり、キーはIDであり、trueアイテムがチェックされている場合の値です。この状態は、チェックを外したり再帰的にチェックしたりする必要がないため、再帰的ではありません。このメニュー項目の下にあるものがチェックされていることを示すインジケーターを表示する場合、簡単な解決策は、チェックボックスの状態をメニューの状態のように再帰的に変更することです。

ツリーをレンダリングする

<Menu>状態ダウンコンポーネントパスの<MenuItemContainer>レンダリング、<MenuItem>秒。

function MenuItemContainer({
  openMenus,
  onMenuToggle,
  checkedMenus,
  onChecked,
  menuItems = []
}) {
  if (!menuItems.length) return null;

  const renderMenuItem = menuItem => (
    <li key={menuItem.id}>
      <MenuItem
        openMenus={openMenus}
        onMenuToggle={onMenuToggle}
        checkedMenus={checkedMenus}
        onChecked={onChecked}
        {...menuItem}
      />
    </li>
  );

  return <ul>{menuItems.map(renderMenuItem)}</ul>;
}

<MenuItemContainer>コンポーネントは、元のコンポーネントからの非常に異なるものではありません。<MenuItem>コンポーネントはいますが、少し違って見えるん。

class MenuItem extends Component {
  constructor(props) {
    super(props);
    this.handleToggle = this.handleToggle.bind(this);
    this.handleChecked = this.handleChecked.bind(this);
  }

  render() {
    const {
      children,
      name,
      id,
      openMenus,
      onMenuToggle,
      checkedMenus,
      onChecked
    } = this.props;

    const isLastChild = !children;
    return (
      <Fragment>
        <Button onClick={isLastChild ? this.handleChecked : this.handleToggle}>
          {name}
        </Button>
        {isLastChild && (
          <Input
            addon
            type="checkbox"
            onChange={this.handleChecked}
            checked={!!checkedMenus[id]}
            value={id}
          />
        )}

        <Collapse isOpen={openMenus ? !!openMenus[id] : false}>
          <MenuItemContainer
            menuItems={children}
            // Pass down child menus' state
            openMenus={openMenus && openMenus[id]}
            onMenuToggle={onMenuToggle}
            checkedMenus={checkedMenus}
            onChecked={onChecked}
          />
        </Collapse>
      </Fragment>
    );
  }

  handleToggle() {
    this.props.onMenuToggle(this.props.id);
  }

  handleChecked() {
    this.props.onChecked(this.props.id);
  }
}

ここで重要な部分はこれです:openMenus={openMenus && openMenus[id]}。メニュー状態全体を渡すのではなく、現在のアイテムの子を含む状態ツリーのみを渡します。これにより、コンポーネントは開いたり折りたたんだりする必要があるかどうかを非常に簡単に確認できます。オブジェクトから独自のIDが見つかったかどうかを確認するだけopenMenus ? !!openMenus[id] : falseです()!

また、トグルボタンを変更して、メニューの最も深い項目である場合は、メニューの状態ではなくチェックボックスをトグルするように変更しました。

私はまた、利用することが!!強要にここ{}undefinedにメニュー状態からtruefalse。これが私が彼らが真実か偽物かどうかだけが重要であると私が言った理由です。reactstrapコンポーネントが明示したいように見えたtruefalse、それはありますなぜこれほどのことではなく、truthy / falsyの。

そして最後に:

ReactDOM.render(
  <Menu initialOpenMenuId="3.1.1.1" initialCheckedMenuIds={["3.1.1.1.1"]} />,
  document.getElementById("root")
);

ここでは、初期状態をに渡し<Menu>ます。initialOpenMenuIdまた、配列にすることができ(またはinitialCheckedMenuIds単一の文字列かもしれない)が、これは質問のスペックに合わせて。

改善の余地

現在のソリューションでは、onMenuToggleonCheckedコールバックなどの多くの状態と、checkedMenus再帰的ではない状態が渡されます。これらは、Reactのコンテキストを利用できます。


1
多くの時間を費やして、非常に詳細なソリューションを提供してくれてありがとう..

0

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 openMenuId = "3.1.1.1.1";

const {Component, Fragment} = React;
const {Button, Collapse, Input} = Reactstrap;

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

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

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

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

class MenuItem extends Component {
  constructor(props) {
    super(props);
    this.state = {isOpen: false};
    this.toggle = this.toggle.bind(this);
  }

  render() {
    let isLastChild = this.props.children ? false : true;
    let {isOpen} = this.state;
    if(openMenuId.startsWith(this.props.id)){isOpen = true;}
    return (
      <Fragment>
        <Button onClick={this.toggle}>{this.props.name}</Button>
        <Fragment>
          {isLastChild ? <Input type="checkbox" checked={openMenuId === this.props.id} value={this.props.id} /> : ''}
        </Fragment>
        <Collapse isOpen={isOpen}>
          <MenuItemContainer menuItems={this.props.children} />
        </Collapse>
      </Fragment>
    );
  }

  toggle() {
    this.setState(({isOpen}) => ({isOpen: !isOpen}));
  }
}

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>


コードダンプは有用な答えではありません。言う何をあなたがした、とどのようにそれが動作します。
TJクラウダー

@TJCrowder、開いたメニュー項目を閉じることもできません。また、この回答には多くの情報がないと言ったように、前に要求されたように、結果を得るのを手伝っていただけませんか?

このソリューションは私の問題を完全に解決しません。メニュー3の最後のレベルを開きますが、再度折りたたむことはできず、他のチェックボックスをオンにしてチェックすることもできません。予想通り本格的な」トンの仕事...

0

最初に特定のメニューを開く必要があると仮定すると、MenuItemコンポーネントがブールプロパティを期待defaultOpenするように設定し、それを使用して初期を設定できますisOpen

次にmenuItems、ロード時にこのプロパティを設定するだけです。

import React from 'react'
import { Button, Collapse, Input } from 'reactstrap';
import 'bootstrap/dist/css/bootstrap.min.css';

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 openMenuId = "3.1.1.1";

const {Component, Fragment} = React;

function setDefaultOpen(menuItems, openMenuId) {
  if(!menuItems) return
  const openMenuItem = menuItems.find(item => openMenuId.startsWith(item.id))
  if(!openMenuItem) return
  openMenuItem.defaultOpen = true
  setDefaultOpen(openMenuItem.children, openMenuId)
}

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

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

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

function MenuItemContainer(props) {
  if (!props.menuItems.length) return null;

  const renderMenuItem = menuItem =>
    <li key={menuItem.id}><MenuItem {...menuItem} /></li>;

  return <ul>{props.menuItems.map(renderMenuItem)}</ul>;
}
MenuItemContainer.defaultProps = {menuItems: []};

class MenuItem extends Component {
  constructor(props) {
    super(props);
    this.state = {isOpen: props.defaultOpen};
    this.toggle = this.toggle.bind(this);
  }

  render() {
    let isLastChild = this.props.children ? false : true;
    return (
      <Fragment>
        <Button onClick={this.toggle}>{this.props.name}</Button>
        <Fragment>
          {isLastChild ? <Input type="checkbox" value={this.props.id} /> : ''}
        </Fragment>
        <Collapse isOpen={this.state.isOpen}>
          <MenuItemContainer menuItems={this.props.children} />
        </Collapse>
      </Fragment>
    );
  }

  toggle() {
    this.setState(({isOpen}) => ({isOpen: !isOpen}));
  }
}

最初のレンダリング後にメニュー項目を開く機能が必要な場合はMenuItem、制御されたコンポーネントを作成する必要があります。

つまりisOpen、親に状態をプルアップし、MenuItemそのIDを引数として渡してクリック時に呼び出すコールバック関数とともに、それをプロップとしてコンポーネントに渡します。親のコールバック関数はisOpen、指定されたIDを持つアイテムのプロパティをその状態で切り替えます。


-1

ただクラスを追加.actve、それはあなたのrequirmentに応じて、その後、追加したり、他のあなたがしたいとスタイルをscriptあなたが通常使用している場合はjs、その後document.querySelector("youElementClassOrId").classList.toggle("idOrClassYouWantToToggle")。これはうまくいくと思います


この私が必要だもの...またトグルと作業罰金が..私は値とチェックボックスがオンにすると、チェックを1から親レベル件までメニューを開くために必要と比較する必要はありません...ん
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.