当前位置: 首页 > 面试题库 >

默认情况下,根据ID打开可折叠菜单

钱凌
2023-03-14
问题内容

我正在制作一个嵌套菜单和子菜单,到目前为止,所有操作都已完成。.现在,我需要使此可折叠菜单默认根据给定的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>

需求:

我有一个ID值存储在const openMenuId = "3.1.1.1.1";父组件中(您可以在loadMenu数组变量下面查看此变量)。

即使有多个子菜单,此ID也将仅属于上一级子ID,因此肯定会有一个复选框,以便需要选中此复选框,并且需要打开父级菜单。

例如..,

正如openMenuId一样"3.1.1.1.1",因此很显然,由于and复选框的值在此处具有匹配项three,因此Three - one - one - one - one需要检查菜单的最后一个子级别openMenuId。然后,需要将各个菜单和子菜单扩展到最后一个级别。

这仅用于所访问页面上的默认行为,因此该用户可以退回并能够检查任何其他菜单中的任何其他复选框。。但是,在访问该页面时,我将有一个需要默认打开的特定ID,并且需要在复选框中选中。

请通过比较作为道具传递的ID并检查各个菜单来帮助我获得打开各个菜单的结果。

奋斗了很久,所以请帮助我。。在此先多谢。


问题答案:

真是个好问题!我真的很高兴为此提出一个解决方案

当您想为菜单状态和复选框状态都赋予初始状态时,我认为在<Menu>级别(甚至更高!)上控制这两种状态都是一个好主意。这不仅使从父级定义初始状态变得容易,而且如果将来需要一些更复杂的菜单或复选框行为,还可以为您提供更大的灵活性。

由于菜单的结构是递归的,所以我认为为菜单状态设置递归结构效果很好。在开始编写代码之前,这里有一个简短的GIF,希望可以帮助解释该状态:

演示版

视频显示菜单的三列:菜单,菜单状态为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 (

      <div

        style={{

          display: "flex",

          flexDirection: "row",

          columnCount: 3,

          justifyContent: "space-between"

        }}

      >

        <div style={{ paddingTop: "10px" }}>

          <MenuItemContainer

            openMenus={openMenus}

            menuItems={menuItems}

            onMenuToggle={this.handleMenuToggle}

            checkedMenus={checkedMenus}

            onChecked={this.handleChecked}

          />

        </div>

        <div style={{ padding: "10px", marginLeft: "auto" }}>

          <p>Menu state</p>

          <pre>{JSON.stringify(openMenus, null, 2)}</pre>

        </div>

        <div style={{ padding: "10px", width: "177px" }}>

          <p>Checkbox state</p>

          <pre>{JSON.stringify(checkedMenus, null, 2)}</pre>

        </div>

      </div>

    );

  }



  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>

回答

下面的代码演练。

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>

演练

Before I start, I must say that I’ve taken the liberty to change some of the
code to use modern JavaScript features like object
destructuring
,
array destructuring,
rest
and default values.

Creating the state

So. Since the IDs of the menu items are numbers delimited by a dot, we can
take advantage of this when constructing the state. The state is essentially a
tree-like structure, with each sub-menu being a child of its parent, and the
leaf node (“last menu” or “deepest menu”) having the value of either {} if
it’s expanded, or undefined if not. Here’s how the initial state of the menu
is constructed:

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

Let’s take this apart bit by bit.

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

These are just convenience functions that we define so we can easily change
the value we use to represent an expanded and unexpanded node. It also makes
the code a little bit more readable compared to just using literal {} or
undefined in the code. The expanded and unexpanded values could just as well
be true and false, what matters is that the expanded node is
truthy and the
unexpanded node is falsy. More about that later.

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

These functions let us toggle or expand a specific menu in the menu state. The
first parameter is the menu state itself, the second is the string ID of a
menu (e.g. "3.1.1.1.1"), and the third is the function that does the
replacing. Think of this like the function you pass to .map(). The replacer
functionality is separated from the actual recursive tree iteration so that
you can easily implement more functionality later - for example, if you want
some specific menu to be unexpanded, you can just pass in a function that
returns 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);
};

This function is used by the two previous ones to provide a cleaner interface.
The ID is split here by the dots (.) which gives us an array of the ID
parts. The next function operates on this array instead of the ID string
directly, because that way we don’t need to do .indexOf('.') shenanigans.

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

The replaceNode function is the meat of the matter. It is a recursive
function that produces a new tree from the old menu tree, replacing the old
target node with the provided replacer function. If the tree is missing parts
from in between, e.g. when the tree is {} but we want to replace the node
3.1.1.1, it creates the parent nodes in between. Kind of like mkdir -p if
you’re familiar with the command.

So that’s the menu state. The checkbox state (checkedMenus) is basically
just an index, with the key being the ID and the value true if an item is
checked. This state is not recursive, since they don’t need to be unchecked or
checked recursively. If you decide that you want to display an indicator that
something under this menu item is checked, an easy solution would be to change
the checkbox state to be recursive like the menu state.

Rendering the tree

The <Menu> component passes down the states to <MenuItemContainer>, which
renders the <MenuItem>s.

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

The <MenuItemContainer> component is not very different from the original
component. The <MenuItem> component does look a little bit different,
though.

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

Here the crucial part is this: openMenus={openMenus && openMenus[id]}.
Instead of passing down the entire menu state, we only pass down the state
tree which contains the current item’s children. This allows the component to
very easily check if it should be open or collapsed - just check if its own ID
is found from the object (openMenus ? !!openMenus[id] : false)!

I also changed the toggle button to toggle the checkbox instead of the menu
state if it’s the deepest item in the menu - if this is not what you’re
looking for, it’s pretty quick to change back.

I also make use of !! here to
coerce {} and undefined from the menu state into true or false. This
is why I said it only matters whether they’re truthy or falsy. The
reactstrap components seemed to want explicit true or false instead of
truthy/falsy, so that’s why it’s there.

And finally:

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

Here we pass the initial state to <Menu>. The initialOpenMenuId could also
be an array (or initialCheckedMenuIds could be a single string), but this
fit the question’s spec.

Room for improvement

The solution right now passes down lots of state all the way down, such as the
onMenuToggle and onChecked callbacks, and the checkedMenus state which
is not recursive. These could make use of React’s
Context.



 类似资料:
  • 我实现了一个在另一个堆栈溢出post上找到的方法,允许使用jQuery展开和折叠表行。该方法很简单,工作正常,但我遇到了缺省情况下扩展行的问题。我如何使这些在页面加载时显示为折叠的,以便用户可以决定展开哪些? 作为一个附带说明,有没有什么方法使折叠/展开看起来更平滑,而不是只是瞬间打开或关闭? 谢谢!

  • 问题内容: 我正在进行实验,发现以下代码可以在Eclipse上编译并正常运行(版本ID:20090920-1017,不确定确切的编译器版本): 在Eclipse中编译并运行时,此命令会正常打印并退出。 对于编译器,这将按预期方式抛出。 那么Eclipse Java编译器中有bug吗? 问题答案: 这是一个错误。这是根据 Java语言规范,第三版 的声明的指定行为: [JLS 14.11 声明](h

  • 在Hybris Backoffice中,当选择任何类型节点(例如产品、客户等)时,默认情况下简单搜索是可见的。然后用户需要点击图标打开高级搜索。有很多例子可以禁用简单搜索,只显示高级搜索小部件。然而,我需要的是交换简单和高级搜索的可见状态,即: 支持简单搜索和高级搜索 默认情况下,高级搜索可见 单击按钮,高级搜索将折叠,简单搜索可见 敬请指点。

  • 是否可以默认打开所有信息窗口。我尝试了以下方法,但不起作用:

  • 我使用此链接在Windows设置中的git bash中启用conda 但是,每当我从上下文菜单打开git bash时,它都不会默认激活base环境。每当我在Windows中打开bash终端时,如何让它默认激活base?

  • 默认情况下,我们的路由器是Yaf_Router, 而默认使用的路由协议是Yaf_Route_Static,是基于HTTP路由的, 它期望一个请求是HTTP请求并且请求对象是使用Yaf_Request_Http