【宜搭】树形控件的保姆级使用指南

关键词: 异步加载 、递归方法、call()函数、浅拷贝

截止到目前,总结我近段时间使用宜搭的经验来看,我认为树形控件的使用是比较困难的,相关代码也是比较难以理解的。

主要原因在于,当前端从后端拿到返回数据之后,如果相应的json数据并不完全符合树形控件所要求的数据格式,就需要自己重新按需求构造,本文内容就想总结一下宜搭的树形控件的使用及相关代码。

关键词所包含的是本人在使用过程中所碰到的一些难点,相关代码和理解难点会在接下来的内容中详细解释。

以下内容可能会涉及到的连接器/远程API数据源以及变量的名称如下:
在这里插入图片描述

第一部分:数据格式分析

首先, 观察树形控件的数据格式如下:

[
  {
    "children": [
      {
        "children": [
          {
            "children": [
              {
                "label": "0-0-0-0-0",
                "key": "0-0-0-0-0"
              }
            ],
            "label": "0-0-0-0",
            "key": "0-0-0-0"
          },
          {
            "label": "0-0-0-1",
            "key": "0-0-0-1"
          }
        ],
        "label": "0-0-0",
        "key": "0-0-0"
      },
      {
        "children": [
          {
            "label": "0-0-1-0",
            "key": "0-0-1-0"
          },
          {
            "label": "0-0-1-1",
            "key": "0-0-1-1"
          }
        ],
        "label": "0-0-1",
        "key": "0-0-1"
      }
    ],
    "label": "0-0",
    "key": "0-0"
  }
]

对上述数据格式的理解如下:

  1. 包含了key、label 和 children,说明提供的展示数据需要包含这三种属性才能显示;
  2. []说明同等级的树形控件节点为数组类型{}说明数组里的数据为json类型
  3. children属性下展示的该节点下与父节点有相似数据格式的子节点的数据(父节点子节点表示的是数据之间的从属关系
  4. key属性为节点的唯一标识,label属性是展示在控件上的节点数据

其次, 假设数据是通过接口 (远程API或者连接器) 返回而非直接在页面提供数据,且接口的数据格式如下:

[
	{
    parentId:xxx,
    name:xxx,
    id:xxx
	},
	{
	parentId:yyy,
	name:yyy,
	id:yyy
	}
]

当parentId属性为空(null)时,返回的是父节点;当parentId属性的值为指定ID时,返回的是该父节点下的子节点的json数据。

第二部分:数据格式构造

接口返回的json数据中并没有我们想要的属性,所以需要我们自己构造。

接口返回数据格式的改造在宜搭有两种方法,一种是通过连接器或远程API下的didFech函数,另一种是在页面通过js进行数据格式修改,具体内容如下。

function didFetch(content) {
  let treeData = [];  //构造一个空数组
  if (content.serviceReturnValue && content.serviceReturnValue.length > 0)  //此处表示数据源从后端返回的数据存储在content对象下的serviceReturnValue属性下,且该属性下存储的数据类型也为数组。
    content.serviceReturnValue.forEach(item => {
      treeData.push({
        key: item.id,
        label: item.name
      })
    })  // *
  return treeData; // 重要,需返回 treeData数据才能拿到经过处理后的数据
}

这里对“*”部分的作用解释如下:

  1. 这里 forEach 函数为javaScript的遍历函数;
  2. 这里 “=>”箭头函数 保证了{}内执行的代码和 forEach 函数在 相同的域(代码在逻辑上的执行空间) ,所以能使调用到的 itemserviceReturnValue 里的每一项,其 idname 属性能够分别赋值给 keylabel 属性;
  3. push()函数 为将 封装为json数据的对象 按先后顺序每次从 尾部 插入到 treeData 中。

didFetch 在宜搭页面中的数据源创建窗口的位置如下:
在这里插入图片描述

构造完成之后,每当我们调用这个数据源时,就会触发该函数,使得返回的数据都是经过处理后的数据,元素值是带有 key label 属性的 json 数据的数组。请求函数如下所示。

export function getTreeData(Id, callback){
	let params = {
	"Query":{
			parentId: Id
			}
		}
	this.dataSourceMap.getTreeNode.load({
		inputs: JSON.stratify(params)
	}).then(res => {
		callback(res)
	}).catch(err => {
		callback([])
	})
}  //此函数除了传入id参数,还传入回调callback函数,这主要是为了后续插入子节点作准备,如果报错,callback函数将被传入空数组。

第三部分:子节点的构造

由于数据是通过 parentId 逐步查询获得的,而不是一次性获得所有的节点数据,所以,此时属性控件需要开启异步加载

注意:

  1. 之所以后端接口不设计为一次性获取所有节点数据,其目的是为了防止由于一次性加载数据量过大,导致页面加载过慢,影响用户体验;
  2. 异步加载 是指在加载页面属性控件数据的同时执行插入子节点的代码,而不是先执行代码,再展示页面数据。

每当拿到父节点的ID时,就根据用户所选择展开的节点的ID去查询其下属的子节点数据。这里使用到了insertChildren函数matchNode函数前者的作用是更新子节点,后者是用于构造符合树形控件使用规范的子节点的json数据。 具体代码如下所示。

// insertChildren函数
export function insertChildren(key, children){
	const treeData = this.state.treeData_1;
	const node = this.matchNode(key, treeData);
	if(!node) {
		return;
	}
	node['children'] = children;
	this.setState({
		treeData_1 : [...treeData]
	})
}

//  matchNode函数(递归函数)
export function matchNode(key, nodes){
	for(let i=0;i<nodes.length;i++){
		if(nodes[i].key === key) {
			return nodes[i];
		}
		if(nodes[i].children){
			const result = matchNode(key, nodes[i].children);
			if(result) {
				return result;
			}
		}
	}
	return null;
}

insertChildren函数使用了浅拷贝,而matchNode函数使用了递归。以下分别结合两个函数所涉及到的难点进行解析:

  1. insertChildren函数
    • 在该函数中,node常量的指针只是指向了全局变量treeData_1所在的内存地址,并没有对treeData_1的数据进行重新拷贝,存放在另一个内存地址中。
    • […treeData]也是浅拷贝,指向同一个内存地址。
  2. matchNode函数
    • 递归函数利用的是数据结构中堆栈的原理,即“后进先出”。所以当匹配到相同的父节点的key值或者是最终找不到对应节点,都会触发return,然后遵循堆栈的规则从最后一层开始往上返回值。

树形控件的异步加载函数如下:

//  需要和树形控件异步加载部分进行函数绑定
export function loadData(data){
	if(data.props.children){
		return new Promise((resolve) => {
			setTimeout(resolve,0);
		});
	}else{
		const key = data.props.eventKey;
		return new Promise((resolve) => {
			this.getTreeData(key, res => {
				this.insertChildren.call(this, key, res);
				setTimeout(resolve, 0);
			})
		});
	}
}

代码作用的解析如下:

  1. 参数data的值为绑定树形控件时,控件传来的数据,该数据包含了父节点的信息;
  2. Promise返回的是一个Promise对象,这是在创建异步加载函数时自带的,应该是宜搭规定异步加载需要将数据封装为一个Promise对象才可用 (等我看看官方有无详细解释)
  3. 通过判断data.props.children是否存在,确定是否执行插入insertChildren函数。此处用到了callback函数,外层函数执行如果拿到key对应的子节点的数据将返回给callback并传入子函数内被调用;
  4. call()函数:重新定义子函数的this的指向对象——this指向全局,所以在子函数可以直接调用 全局变量treeData

第四部分:其他注意事项

当需要获取所选的节点的相关信息并作为其他函数的参数进行调用时,可以考虑构建全局变量activeTreeNode,同时给树形控件绑定select函数,将select函数传入的数据赋值给变量,之后只需调用变量即可拿到节点的数据。相关的函数代码如下:

export function onTreeNodeSelect(selectedKeys, extra){
  // console.log(extra)
  var node = extra.node ? extra.node.props : extra
  this.setState({activeTreeNode: {
    key: node._key || node.key,
    label: node.label,
    parentId: node.parentId,
  }})
}

如上代码所示:

  1. selectedKeysextra参数及其参数值都是选中树形控件节点后提供的。
  2. 有必要先用console.log()函数打印extra,观察其数据结构。观察到它可能有两种形式存储数据,一种是存放在node属性下,一种是直接存储在extra下,所以进行判断,如果extra.node不为空,node被赋值为前者,否则为后者,后面就是给变量赋值,数据类型为json

最后,注意

  • loadData函数需要绑定在异步加载下,onTreeNodeSelect函数需要绑定在属性控件的点击事件下。
  • 想要页面加载时就加载树形控件的数据,需要在didmount()下调用getTreeData()函数,函数体如下:
setTimeout(() => {
    this.getTreeData(null, data => {
      if(data.length > 0){
        this.setState({treeData_1: data});
    })
  }, 500)

PS: id为null获取的是父节点,然后将返回的数据赋值给变量treeData_1,treeData_1记得绑定到树形控件的数据源处。



好了,以上就是本文全部内容。如果觉得有帮助的话,请点个“赞”吧,我将持续更新,尽情期待。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ag+Cu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值