React中怎么实现对树形表格的增删改操作,方法是什么

Admin 2022-07-13 群英技术资讯 675 次浏览

这篇文章主要讲解了“React中怎么实现对树形表格的增删改操作,方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“React中怎么实现对树形表格的增删改操作,方法是什么”吧!


最近因为业务接触了antd,使用antd完成一个复杂的树形表格的显示以及修改。在这其中遇见了不少坑,很多功能antd只写了初步的功能,更为细化的功能只能自己完善。踩过的坑都写在了这里。

树形表格的显示

在antd中对于表格的key值有着严格的控制,每一个row都必须有一个独一无二的key值,可以是数字也可以是字符串。这一点和我曾经使用过得iview有着很大的区别。react使用key来代表每一行是为了避免重新渲染的问题,这个优化也在实际的开发中带来了不少的问题。比如新建行时需要自定义新key。

下面直接上一下代码及代码效果,这是一个三级的树形表格,且其中包含二级标题。

最终效果 ​

colums标题: 简易版标题,随着功能的增加,我们将增加colums的复杂度。

let columns = [
 {
  title: '题目',
  dataIndex: 'text'
 },
 {
  title: '类型',
  children: [
   {
    title: '一级',
    dataIndex: 'text1'
   },
   {
    title: '二级',
    dataIndex: 'text2',
   }]
 },
 {
  title: '内容',
  dataIndex: 'content'
 },
 {
  title: '答案',
  dataIndex: 'answer',
 },
 {
  title: '类型',
  dataIndex: 'mark_type',
  className: 'line'
 },
 {
  title: '版本',
  dataIndex: 'version',
  className: 'line'
 },
 {
  title: '一级内容点',
  dataIndex: 'value1',
  className: 'line'
 },
 {
  title: '二级内容点',
  dataIndex: 'value2',
  className: 'line'
 },
 {
  title: '操作',
  key: 'action',
  width: 205
 }
];

data数据:

let data = [{
   "key": 1,
   "text": "题目一",
   "children": [{
    "key": 11,
    "text1": "数学一",
    "children":[]
   }, {
    "key": 12,
    "text1": "语文一",
    "value1": "语文",
    "children": [{
     "key": 121,
     "value2": "选择",
     "text2": "选择题",
     "content": "题目内容",
     "answer": "A",
     "mark_type": "1",
     "version": "1"
    },{
     "key": 122,
     "value2":"填空",
     "text2": "填空题",
     "content": "题目内容",
     "answer": "梅花",
     "mark_type": "1",
     "version": "1"
    },{
     "key": 123,
     "value2": "阅读",
     "text2": "阅读题",
     "content": "题目内容",
     "answer": "野蛮生长",
     "mark_type": "1",
     "version": "1"
    },{
     "key": 124,
     "value2": "文言文",
     "text2": "文言文",
     "content": "题目内容",
     "answer": "滕王阁序",
     "mark_type": "1",
     "version": "1"
    }],
   }],
  }, {
   "key": 2,
   "text": "题目二",
   "children": [ {
    "key": 21,
    "text1": "英语一",
    "value1": "英语",
    "children": [{
     "key": 211,
     "value2": "完型",
     "text2": "完形填空",
     "content": "题目内容",
     "answer": "ABC",
     "mark_type": "2",
     "version": "1"
    },{
     "key": 212,
     "value2": "一级代码",
     "text2": "选择",
     "content": "题目内容",
     "answer": "D",
     "mark_type": "2",
     "version": "1"
    }],
   }],
  }];

增加子项数据

增加子项数据使用操作中的增加按钮进行增加,增加按钮设置为图形,更为形象具体清晰化。

 //button样式使用antd自带icon样式
<Button type="primary" shape="circle" icon="plus" size={'small'}
   onClick={this.handleAdd.bind(this, record)}
 />

注意事项:

1、对于最子项来说没有增加子项选择,需要对不同数据行进行不同处理

2、不同数据行新增数据的内容不同,字段也不同

3、新增之后需要点击确认或者取消

4、加入数据点击确认之后需要添加于当前key下的children中

本想使用antd自带的editable属性,但是这个属性不支持二级标题的编辑,所以只有自己写render

这个问题,在7102年就提出来了,但是已经8102年了快9102年都没有更新。

新增一级类型题目样式

新增二级类型题目样式

这里就部分render代码:

//分级标题
{
 title: '类型', 
 children: 
 [{
  title: '一级',
  dataIndex: 'text1',
  render: (text, record) => {
   if (this.state.isEditing && this.state.editingOneKey === record.key)
    return <Input defaultValue={text} onChange={(e) => { this.changeEdit(e, record.key, 'text1') }} />
   return text
  }
 },
 {
  title: '二级',
  dataIndex: 'text2',
  render: (text, record) => {
   if (this.state.isEditing && this.state.editingTwoKey === record.key)
    return <Input defaultValue={text} onChange={(e) => { this.changeEdit(e, record.key, 'text2') }} />
   return text
  }
 }]
}

//select选择
{
 title: '内容',
 dataIndex:'content',
 render:(text, record) => {
  if (text === 0)
   text = '内容一';
  if (text === 1)
   text = '内容二';
  if (this.state.isEditing && this.state.editingTwoKey === record.key) {
   return <Select defaultValue={text} onChange={(e) => {
    this.changeEdit(e, record.key, 'flag')
   }} getPopupContainer={triggerNode => triggerNode.parentNode}>
    <Option value='0'>有效</Option>
    <Option value='1'>无效</Option>
   </Select>
  }
  return text
 }
}

注意展开

如果对一行未展开的行下新增数据,那么无法看到打开的编辑状态是一个非常糟糕的问题。所以我们需要在新增子项的时候自动展开父项。

//steate初始化 
expandedRows: [] //展开行
//render 初始化
<Table
  bordered
  expandedRowKeys={this.state.expandedRows}
  onExpandedRowsChange={this.changeExpandedRows.bind(this)}
/>

//自动展开变化,获取当前展开行
changeExpandedRows = (expandedRows) => {
  this.setState({
   expandedRows
  })
 };

//增加函数中
let rows = this.state.expandedRows;
rows.push(record.key);
this.setState({
 expandedRows: rows
});

删除行数据

删除在antd中相较比较简单,因为antd的table每行都有key属性,key是唯一且必须的属性,所以只要过滤掉包含目的key的数据即可视为删除。因为这是树形数据,普通的遍历无法进行操作,所以使用深度优先遍历来进行数据处理,也可以使用广度优先,根据数据来变更。

handleDelete = (key) => {
  let data = this.state.data;
  data = dsFilter(data, key);

  this.setState({
   data
  });

  function dsFilter(dealData, dealKey) {
   for (let i = 0; i < dealData.length; i++) {
    if (dealData[i].children && dealData[i].children.length > 0) {
     dealData[i].children = dsFilter(dealData[i].children, dealKey);
    }
   }
   return dealData.filter(item => item.key !== dealKey);
  }
 };

修改某些字段

修改和新增的colums相同,不过修改需要保存之前的值。需要在input或select中设置defaultValue={text}来保证初始值相同

保存

无论是新增修改还是删除,都需要进行保存。这里使用和 来代表确定和取消。

同时需要注意,如果在一行的编辑中点击其他行的操作,需要将之前行的操作视为取消。

<div>
 <Button shape="circle" icon="check" size={'small'} style={{ backgroundColor: '#65BF34', color: '#FFF', border: 'none' }}
   onClick={this.saveEdit.bind(this, record.key)}
 />
 <Button shape="circle" icon="close" size={'small'} style={{ backgroundColor: '#FF3333', color: '#FFF', border: 'none' }}
   onClick={this.cancelEdit.bind(this, record.key)}
 />
</div>

修改顺序

修改顺序就是修改数据在数组中的顺序,使用简单的temp进行交换即可。

shiftUp = (key) => {
  let data = this.state.data;
  data = dsShift(data, key);
  this.setState({
   data
  });

  function dsShift(dealData, dealKey) {
   for (let i = 0; i < dealData.length; i++) {
    if (dealData[i].children && dealData[i].children.length > 0) {
     dealData[i].children = dsShift(dealData[i].children, dealKey);
    }
    if (dealData[i].key === dealKey) {
     if (i === 0) {
      message.warning('该行已置顶!');
      break;
     }
     let temp = dealData[i - 1];
     dealData[i - 1] = dealData[i];
     dealData[i] = temp;
     break;
    }
   }
   return dealData;
  }
 };

 shiftDown = (key) => {
  let data = this.state.data;
  data = dsShift(data, key);
  this.setState({
   data
  });

  function dsShift(dealData, dealKey) {
   for (let i = 0; i < dealData.length; i++) {
    if (dealData[i].children && dealData[i].children.length > 0) {
     dealData[i].children = dsShift(dealData[i].children, dealKey);
    }
    if (dealData[i].key === dealKey) {
     if (i === dealData.length - 1) {
      message.warning('该行已置尾!');
      break;
     }
     let temp = dealData[i + 1];
     dealData[i + 1] = dealData[i];
     dealData[i] = temp;
     break;
    }
   }
   return dealData;
  }
 };

总结

在进行表格数据操作中,需要进行变量的保存,这里就没有多写了。总的来说antd是个好组件,大部分功能都很齐全,但是很多细节还是需要自己进行完善。

补充知识:Antd(Ant-design),嵌套子表格(expandedRowRender)的异步获取数据

使用阿里的ant-design开源框架,要在表格里面嵌套子表格,需要在用户点击父表格的一行数据后,获取该行的key,然后去异步请求后台的数据用来填充子表格的内容。

如果这样写(省略无关代码):

expandedRowRender = (record) => {
 
  dispatch({
    type: 'flow/getPlanList',
    payload: {
      contractId: record.contract_id, // 该参数是从父表格带过来的key
    },
    callback: () => {
      const {
       flow: { data },
      } = this.props; 
 
      this.setState({
       secData: data.list,
      });
 
      console.log("返回数据(PlanList):" + JSON.stringify(this.state.secData));
    }
  });
  
  return (
    <Table
      columns={secColumns}
      dataSource={this.state.secData}
      pagination={false}
    />
  );
};
 
render() {
  return(
    <Card>
     {this.renderForm()}
     <div>
       <Table
        expandedRowRender={this.expandedRowRender}
        loading={loading}
        rowSelection={rowSelection}
        dataSource={list}
        columns={columns}
        pagination={paginationProps}
        scroll={{ x: 2500}}
        size = 'middle' 
        expandRowByClick={true}
        onSelect={this.seFn}
       />
     </div>
    </Card>
  ) 
}

则会出现不断的发起请求的现象:

这是因为,expandedRowRender 实际上是在 Table 组件的 render 方法中调用的,React render 中用 dispatch 会造成重复调用的问题,dispatch -> setState -> render -> dispatch -> setState -> render,循环往复。所以应该把 dispatch 放在 onExpand 中。

onExpand = (expanded, record) => {
  const { dispatch } = this.props;
 
  dispatch({
    type: 'flow/getPlanList',
    payload: {
     contractId: record.contract_id,
    },
    callback: () => {
      const {
        flow: { data },
      } = this.props; 
 
      this.setState({
        secData: data.list ,
      });
 
     console.log("返回数据(PlanList):" + JSON.stringify(this.state.secData));
    }
  });
}

但是单纯的这样做,又会带来新的问题,就是子表格的所有数据都变成了相同的。

本人的解决办法是使用键值对。

onExpand = (expanded, record) => {
  const { dispatch } = this.props;
 
  if (expanded === false) {
   // 因为如果不断的添加键值对,会造成数据过于庞大,浪费资源,
   // 因此在每次合并的时候讲相应键值下的数据清空
   console.log("合并!");
   this.setState({
    subTabData: {
     ...this.state.subTabData,
     [record.contract_id]: [] ,
    }
   });
  } else {
   console.log("展开!");
   dispatch({
    type: 'flow/getPlanList',
    payload: {
     contractId: record.contract_id,
    },
    callback: () => {
     const {
      flow: { data },
     } = this.props; 
 
     this.setState({
      subTabData: {
       ...this.state.subTabData,
       [record.contract_id]: data.list ,
      }
     });
 
     console.log("返回数据(PlanList):" + JSON.stringify(this.state.subTabData));
    }
   });
  }
 }

到此这篇关于“React中怎么实现对树形表格的增删改操作,方法是什么”的文章就介绍到这了,更多相关内容请搜索群英网络以前的文章或继续浏览下面的相关文章,希望大家以后多多支持群英网络!
群英智防CDN,智能加速解决方案
标签: 树形表格

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。

猜你喜欢

成为群英会员,开启智能安全云计算之旅

立即注册
专业资深工程师驻守
7X24小时快速响应
一站式无忧技术支持
免费备案服务
免费拨打  400-678-4567
免费拨打  400-678-4567 免费拨打 400-678-4567 或 0668-2555555
在线客服
微信公众号
返回顶部
返回顶部 返回顶部
在线客服
在线客服