如何从对象中递归删除子对象?

Posted

技术标签:

【中文标题】如何从对象中递归删除子对象?【英文标题】:How to remove children objects recursively from object? 【发布时间】:2021-05-29 12:31:19 【问题描述】:

我正在研究解决方案

每当用户单击展开与单击的行相关的数据时,我已经创建了基本的树型表,该表将根据行数据出现在其下方

我已经实现了展开/折叠最多 N 个嵌套级别的基本功能。

但我只遇到一个问题,所以基本上所有行都有基于具有多个值的数组的条件展开按钮

假设它是具有 3 个条目县、市、州的拆分数组

默认加载的数据将从 api 获取,现在我必须检查数组是否有可用的拆分!如果是的话,我会让展开按钮可见

考虑这种情况

const split = ["country","city","state"]

这是 UI 的样子

+ Data_1
+ Data_2

单击按钮 + 新数据表行将根据下一个可用拆分呈现,在我们的例子中是国家/地区,因此视觉表示会像

- Data_1
   Country_1
   Country_2
+ Data_2

这里国家没有展开按钮,因为用户还没有添加下一个拆分,让我们添加城市,并假设用户点击了 Country_1,所以数据会像

    - Data_1
       - Country_1
           City_1
           City_2
       + Country_2
    + Data_2

我的解决方案工作正常,直到这个级别现在可以说用户已从拆分中删除国家/地区和城市的所有节点,并且 - data_1 的图标应更改为 +

这是我的代码

import React, useState, useEffect, useRef, Fragment from "react";
  import _ from "lodash";
  import axios from "axios";

  class TableRowData extends React.Component 
    state = 
      showIcon: false,
      selection: [],
      data: [],
      splitOption: ["campid"]
    ;
    constructor(props) 
      super(props);
    

    componentDidMount() 
      const checkIfSplitExistOnMount = (currentSplit) => 
        const i = _.findIndex(this.state.splitOption, function(el) 
          return el === currentSplit;
        );

        if (this.state.splitOption[i + 1]) 
          return this.state.splitOption[i + 1];
         else 
            return null;
        
      
      const getReportData = () => 
        axios.get("https://jsonplaceholder.typicode.com/users?_start=0&_limit=1").then((res) => 
          const rowData = res.data.map((row) => 
            row.name = this.state.splitOption[0];
            row.isExpanded = false;
            row.currentSplit = this.state.splitOption[0];
            row.nextSplit = checkIfSplitExistOnMount(this.state.splitOption[0])
            row.parentId = 0;
            row.isVisble = true;
            //console.log(row)
            return row;
          );
          this.setState(
            data: rowData
          , () =>  //console.log(this.state.data)
          );
        );
      
      getReportData()
    

    render() 
      // update state function
      const updateState = () => 
        this.setState(
          data: [...this.state.data],
          splitOption: [...this.state.splitOption],
          selection: [...this.state.selection],
        , () => )
      

      // recusively update parent and child
      const recursion = (obj) => 
         let row = obj;
         row.isExpanded = row.isExpanded;
         row.currentSplit = row.currentSplit;
         row.nextSplit = checkIfSplitExist(row.currentSplit)

         if (row.children && row.children.length > 0)  // check if has children
            row.children.forEach(v =>  // if has children do the same recursion for every children
              recursion(v);
            );
         
         return row; // return final new object
       

       const recursionDel = (obj,split) => 
           var row = obj;
           row.currentSplit = row.currentSplit;
           row.nextSplit = checkIfSplitExist(row.currentSplit)
           if (row.children && row.children.length > 0)  // check if has children
             row.children.forEach(v =>  // if has children do the same recursion for every children
               recursionDel(v);
             );
          
          return row; // return final new object
        

      // function to check if next split is there or not if there than return nextsplit
      const checkIfSplitExist = (currentSplit) => 
        const i = _.findIndex(this.state.splitOption, function(el) 
          return el === currentSplit;
        );
        if(i !== -1) 
          if (this.state.splitOption[i + 1]) 
            return this.state.splitOption[i + 1];
            else 
            return null;
          
        

      

      // recursive update whenever split added
      const recursiveUpdate = (data) => 
        const prevData = [...data];
        return prevData.map((row) => 
          const updatedData =  recursion(row);
          return row;
        );
      

      // function to delete child and parent node recursively
      const recursiveDelete = (data,split) => 
        const prevData = [...data];
        return prevData.map((row) => 
          const data =  recursionDel(row,split);
          return row;
        );
      

      const addNewSplit = (split) => 
        const i = _.findIndex(this.state.splitOption, function(el) 
          return el === split;
        );
        if(i === -1) 
            this.setState(
              
                splitOption:[...this.state.splitOption,split]
              ,
              ()=>
                var rowData = recursiveUpdate(this.state.data)
                this.setState(data:rowData)
              
          );
         else 
          const prevData = [...this.state.splitOption];
          var index = prevData.indexOf(split);
          prevData.splice(index,1)
          if(index!==-1) 
            this.setState(
                
                    splitOption:prevData
                ,
                ()=> 
                  var rowData = recursiveDelete(this.state.data,split)
                  this.setState(data:rowData)
                
              )
          
        

      

      // add lazyload expand data
      const ExpandableTableRow = (rows) => 

        const expandRow = (row) => 
          row.children = [
            
              id: "_" + Math.random().toString(36).substr(2, 5),
              name: row.id + "_" + row.nextSplit,
              isExpanded: false,
              parentId: row.id,
              currentSplit: row.nextSplit,
              nextSplit: checkIfSplitExist(row.nextSplit),
              isVisble:true
            , 
              id: "_" + Math.random().toString(36).substr(2, 5),
              name: row.id + "_" + row.nextSplit,
              isExpanded: false,
              parentId: row.id,
              currentSplit: row.nextSplit,
              nextSplit: checkIfSplitExist(row.nextSplit),
              isVisble:true
            
          ];
          row.isExpanded = true;
          updateState();
        ;

        // call whenever - click
        const collapseRow = (row) => 
          delete row.children;
          row.isExpanded = false;
          updateState();
        ;

        // toggle
        const ExpandCollapsToggle = (row, expandRow, collapseRow) => 
          // display +/- only if nextsplit is not undefined or null
          if (row.nextSplit) 
            if (row.isExpanded === true) 
              return (<button type="button" onClick=() => collapseRow(row)>
                -
              </button>);
             else 
              return (<button type="button" onClick=() => expandRow(row)>
                +
              </button>);
            
           else 
            return null;
          
        ;



        if (rows) 
          return rows.map((row) => 
          //  if(!_.isEmpty(row)) 
              return (<Fragment key=row.id>
                <tr key=row.id>
                  <td>
                    <ExpandCollapsToggle row=row expandRow=expandRow collapseRow=collapseRow/>" "
                    row.split
                    - row.id
                  </td>
                  <td>row.name</td>
                </tr>
                <ExpandableTableRow rows=row.children/>
              </Fragment>);
          //  

          );
         else 
          return null;
        
      ;

      const splitData = this.state.splitOption.map((ob) => 
        return (<Fragment key=ob><span>ob</span> > </Fragment>)
      )

      if (this.state.data) 
        return (
          <Fragment>
            splitData <br/>
            <button onClick = ()=>addNewSplit("name")>camp name</button>
            <button onClick = ()=>addNewSplit("os")>os</button>
            <button onClick = ()=>addNewSplit("country")>country</button>
          <ExpandableTableRow rows=this.state.data />
        </Fragment>
        );
       else 
        return null;
      
    
  

  export default TableRowData;

我也创建了codesandbox.io的例子-Link

这是你如何使用 ui 来复制场景

首先点击阵营名称,会出现展开图标 现在如果你想扩展,你可以看到根据拆分的数据 现在再添加一个拆分操作系统或国家/地区,您可以看到带有 2 级行的展开图标 下一步是删除“阵营名称”,这是删除阵营名称时的问题,应根据可用的拆分重新渲染表格,在我们的例子中,用户的所有行应该被删除,+ 图标必须在那里我们有下一个拆分操作系统或国家/地区可用,我使用默认拆分 ID,它将始终存在

【问题讨论】:

请检查答案。 【参考方案1】:
import React,  useState, useEffect, useRef, Fragment  from "react";
import axios from "axios";

const test_data = [
  "id":1,
    "name":"Leanne Graham",
    "username":"Bret",
    "email":"Sincere@april.biz",
    "address":
      "street":"Kulas Light",
      "suite":"Apt. 556",
      "city":"Gwenborough",
      "zipcode":"92998-3874",
      "geo":
        "lat":"-37.3159",
        "lng":"81.1496"
      
    ,
    "phone":"1-770-736-8031 x56442",
    "website":"hildegard.org",
    "company":
      "name":"Romaguera-Crona",
      "catchPhrase":"Multi-layered client-server neural-net",
      "bs":"harness real-time e-markets"
    
];

class TableRowData extends React.Component 

  constructor(props) 
    super(props);

    this.state = 
      showIcon: false,
      selection: [],
      data: [],
      splitOption: ["campid"]
    ;
  

  // function to check if next split is there or not if there than return nextsplit
  checkIfSplitExist = (currentSplit) => 
    const i = this.state.splitOption.indexOf(currentSplit);

    if (i > -1 && this.state.splitOption[i + 1]) 
      return this.state.splitOption[i + 1];
    
    return null;
  

  getReportData = () => 
    // axios.get("https://jsonplaceholder.typicode.com/users?_start=0&_limit=1").then((data) => 
      this.setState(
        data: test_data.map((row) => 
        row.name = this.state.splitOption[0];
        row.isExpanded = false;
        row.currentSplit = this.state.splitOption[0];
        row.nextSplit = this.checkIfSplitExist(this.state.splitOption[0])
        row.parentId = 0;
        row.isVisble = true;
        console.log(row)
        return row;
      )
      );
    // );
  
  
  componentDidMount() 
    this.getReportData()
  

  render() 
    // update state function
    const updateState = () => 
      this.setState(
        data: [...this.state.data],
        splitOption: [...this.state.splitOption],
        selection: [...this.state.selection],
      , () =>  )
    

    const recursionUpdateAndDeleteRow = (parentRow, childRow, split, index = 0) => 
      childRow.children && childRow.children.forEach((r) => 
        recursionUpdateAndDeleteRow(childRow, r, split, index + 1);
      );

      if (parentRow && split.indexOf(childRow.currentSplit) == -1) 
        delete parentRow.children;
      

      childRow.currentSplit = split[index];
      childRow.nextSplit = split[index + 1] || null;
      if (!childRow.children) 
        childRow.isExpanded = false;
      
    

    const recursionUpdateAndDeleteRows = (rows, split) => 
      const _copy = [...rows];
      _copy.forEach((row) => 
        recursionUpdateAndDeleteRow(null, row, split);
      );
      return _copy;
    

    const toggleSplit = (split) => 
      const index = this.state.splitOption.indexOf(split);
      let currentSplitOptions = [...this.state.splitOption];
      if (index > -1) 
        currentSplitOptions.splice(index, 1)
      
      else 
        currentSplitOptions.push(split);
      

      const _data = recursionUpdateAndDeleteRows(this.state.data, currentSplitOptions);

      this.setState(
        splitOption: currentSplitOptions,
        data: _data
      )
    

    // add lazyload expand data
    const ExpandableTableRow = ( rows ) => 

      const expandRow = (row) => 
        row.children = [
          
            id: "_" + Math.random().toString(36).substr(2, 5),
            name: row.id + "_" + row.nextSplit,
            isExpanded: false,
            parentId: row.id,
            currentSplit: row.nextSplit,
            nextSplit: this.checkIfSplitExist(row.nextSplit),
            isVisble: true
          , 
            id: "_" + Math.random().toString(36).substr(2, 5),
            name: row.id + "_" + row.nextSplit,
            isExpanded: false,
            parentId: row.id,
            currentSplit: row.nextSplit,
            nextSplit: this.checkIfSplitExist(row.nextSplit),
            isVisble: true
          
        ];
        row.isExpanded = true;
        updateState();
      ;

      // call whenever - click
      const collapseRow = (row) => 
        delete row.children;
        row.isExpanded = false;
        updateState();
      ;

      // toggle
      const ExpandCollapsToggle = ( row ) => 
        // display +/- only if nextsplit is not undefined or null
        if (row.nextSplit) 
          if (row.isExpanded) 
            return (
              <button type="button" onClick=() => collapseRow(row)>
                -
              </button>
            );
          
          return (
            <button type="button" onClick=() => expandRow(row)>
              +
            </button>
          );
        
        return null;
      ;

      if (rows) 
        return rows.map((row) => 
          return (
            <Fragment key=row.id>
            <tr key=row.id>
              <td>
                <ExpandCollapsToggle
                  row=row
                />
                " "row.split - row.id
              </td>
              <td>row.name</td>
            </tr>
            <ExpandableTableRow rows=row.children />
          </Fragment>
          );
        );
       else 
        return null;
      
    ;

    if (this.state.data) 
      return (
        <Fragment>
          this.state.splitOption.join(', ') <br />
          <button onClick=() => toggleSplit("name")>
            camp name
          </button>
          <button onClick=() => toggleSplit("os")>os</button>
          <button onClick=() => toggleSplit("country")>country</button>
          <br />
          <ExpandableTableRow rows=this.state.data />
        </Fragment>
      );
     else 
      return null;
    
  


export default function App() 
  return (
    <div>
      <TableRowData />
    </div>
  );

在这里工作example

【讨论】:

谢谢@chandan

以上是关于如何从对象中递归删除子对象?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 @ManyToMany 关系中删除与 JPA 和 Hibernate 中的许多子对象的子对象

递归过滤/减少嵌套对象

如何使用无点递归实现使用 Ramda 删除对象中的空值?

如何将道具传递给递归子组件并保留所有道具

如何删除子托管对象上下文中的临时对象?

删除父对象时如何删除由stream.flatMap创建的列表中的子对象?