在React js中打开和关闭递归嵌套列表

3

我希望能够使用React打开/关闭嵌套列表,以便当您单击父li时,子项将被隐藏。这是我用来创建列表的代码。

list.js

class List extends React.Component {

render(){
    return (
        <ul>
            {this.props.list.map(function(item){
                return (
                <li>
                    <ListItem key={item.id} item={item} />
                    <List list={item.children} />
                </li>
                );
            })}
        </ul>
    );
}

list-item.js

class ListItem extends React.Component {

handleCollapse(){
    console.log('Open/Close: ' + this.props.item.display_name);
    return false;
}

handleFilter(){
    console.log('Filter id: ' + this.props.item.id);
    return false;
}

render(){
    return (
        <div>
            <a rel="{this.props.item.id}" onClick={this.handleCollapse.bind(this)}>
                {this.props.item.display_name}  
            </a>
            <input value="" type="checkbox" onClick={this.handleFilter.bind(this)} />
        </div>
    )
}
2个回答

4

您是否正在尝试模拟手风琴行为?如果是,那么可以按照以下方式修改代码。使用组件的状态并切换它以打开和关闭子级。在List类中不要创建<List list={item.children} />,而是在list-item.js中导入(或使用requirelist.js,并根据当前ListItem的状态呈现子List项。

list-item.js

class ListItem extends React.Component {
  //Add this
  constructor (){
      super(...arguments);
      this.state = { showChild:false};
  }

  handleCollapse(){
      console.log('Open/Close: ' + this.props.item.display_name);
      //Add this
      this.setState({showChild:!this.state.showChild});
      return false;
  }

  handleFilter(){
      console.log('Filter id: ' + this.props.item.id);
      return false;
  }

  render(){
      let children;
      if(this.state.showChild) {
          children = (<List list={this.props.item.children} />);
      }

      return (
          <div>
              <a rel="{this.props.item.id}" onClick={this.handleCollapse.bind(this)}>
            {this.props.item.display_name}
              </a>
              <input value="" type="checkbox" onClick={this.handleFilter.bind(this)} />
             //Add this
             {children}
          </div>
      )
  };
}

list.js

class List extends React.Component {

    render(){
        //Removed <List .../>, rest is same just other way of doing same stuff
        let LI = this.props.list.map( (item) => {
            return( <li> <ListItem key={item.id} item={item} /></li>);
        }
     );

        return ( <ul>{LI}</ul>);
  }

};

测试用虚拟数据

var data=[
  {
      id:"1st of Level 1",
      get display_name(){return _name(this.id,this.children)},
      children: [
          {
              id:"1st of Level 1.2",
              get display_name(){return _name(this.id,this.children)},
              children: [
                  {
                      id:"1st of Level 1.2.1",
                      get display_name(){return _name(this.id,this.children)},
                      children:[]
                  }
              ]
          },

          {
              id:"2nd of Level 1.2",
              get display_name(){return _name(this.id,this.children)},
              children:[]
          }
      ]
  },

  {
      id:"2nd of Level 1",
      get display_name(){return _name(this.id,this.children)},
      children:[]
  },

  {
      id:"3rd of Level 1",
      get display_name(){return _name(this.id,this.children)},
      children:[]
   },

   {
       id:"4th of Level1",
       get display_name(){return _name(this.id,this.children)},
       children:[]
   }

 ];

function _name(id,child) {
    if(child.length>0)
        return("I am "+id+" I HAVE CHILDREN");
    else {
        return("I am "+id+" I DON'T HAVE CHILDREN ");
    }    
}


ReactDOM.render(<List list={data} />,document.getElementById('root'));

`


0

递归地执行这个操作是一个不错的方法。你可以给每个列表项元素添加一个 onClick 处理程序。

<li onClick={this._toggleChildrenInit.bind(this, item.id)}>

我不太清楚你的id是什么样子的。但请考虑以下结构:

{id: "0", isOpen: false, children: [
  {id: "0.0", isOpen: false, children: [
    {id: "0.0.0", isOpen: false, children: null},
    {id: "0.0.1", isOpen: false, children: null}
  ]},
  {id: "0.1", isOpen: false, children: null}
]}

每个子项的ID是前一个父级的ID加上一个“.”和该级别内的顺序号。
然后,使用递归,可以检查带有某个X ID的所需项是否可以成为某个项的子项,并以相同方式开始遍历该子项。这样,您就不必遍历树中的所有项。
当然,这可能因应用程序而异,但希望它提供了一种方法。
点击触发递归的代码如下:
_toggleChildrenInit = (data_id) => {
  var result = this._toggleChildren(0, data_id, this.state.listObject);
 this.setState({listObject: result});
}

_toggleChildren = (i, data_id, arr) => {
  if (i <= arr.length-1) {
    if (arr[i].id == data_id && arr[i].children.length > 0) {
      arr[i].isOpen = !arr[i].isOpen;
    } else {
      var newArr = this._toggleChildren(0, data_id, arr[i].children);
      arr[i].children = newArr;
      if (arr[i].id.charAt(0) != data_id.charAt(0)) {
        arr[i].isOpen = false;
      }
    }
    arr = this._toggleChildren(++i, data_id, arr);
  }
  return arr;
}

编辑 - 另一种结构

如果您的对象看起来像这样:

{id: "0", isOpen: false, children: [
  {id: "1", isOpen: false, children: [
    {id: "2", isOpen: false, children: null},
    {id: "3", isOpen: false, children: null}
  ]},
  {id: "4", isOpen: false, children: null}
]}

您可以使用这个替代方案:
_toggleChildrenInit = (data_id) => {
  var result = this._toggleChildren(0, data_id, this.state.listObject);
 this.setState({listObject: result});
}

_toggleChildren = (i, data_id, arr) => {
  if (i <= arr.length-1) {
    if (arr[i].id == data_id && arr[i].children.length > 0) {
      arr[i].isOpen = !arr[i].isOpen;
    } else {
      var newArr = this._toggleChildren(0, data_id, arr[i].children);
      arr[i].children = newArr;
      if (arr[i].id < data_id) {
        arr[i].isOpen = false;
      }
    }
    arr = this._toggleChildren(++i, data_id, arr);
  }
  return arr;
}

是的,几乎可以了。需要进行一些微小的调整。我没有测试过这个,但可以尝试上面的编辑。 - Chris
奇怪。你应该像在你的代码中使用自己的事件处理程序一样使用onClick。尝试在控制台记录this - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接