Ant Design TreeSelect 树选择控件“异步加载” - zptime/blog GitHub Wiki
之前对
Ant Design TreeSelect
树选择控件进行了基础封装,这一篇主要讲异步数据的处理,也顺便讲了一下深度优先遍历(DFS)和广度优先遍历(BFS)。
封装原因:之前部门树数据是一次性加载的, 存在着性能问题,数据量很大的时候,加载很缓慢,而且组件可能崩溃;因此改成异步加载数据,系统中用到的地方很多,不可能每个地方都改一下,进行封装统一处理。
Ant Design TreeSelect 树选择控件二次封装及原理:https://juejin.cn/post/7003280618473668639
-
departSource
:数据源,默认展示根部门和一级部门数据 -
onLoadData
:异步加载数据,根据部门 id 查询直接子部门数据,加载到数据源中 -
treeExpandedKeys
:展开的树节点,默认展开根部门
<template>
<a-tree-select
v-bind="$attrs"
v-on="selectListeners"
class="m-width"
placeholder="请选择"
searchPlaceholder="请输入部门名称"
:treeDefaultExpandAll="false"
:treeData="departSource"
:load-data="onLoadData"
@treeExpand="handleExpand"
:treeExpandedKeys="treeExpandedKeys"
:dropdownStyle="{ maxHeight: '400px' }"
show-search
treeNodeFilterProp="title"
>
</a-tree-select>
</template>
const tree = [
{
id: 1,
name: "根部门",
departNumber: 6,
children: [
{
id: 11,
pid: 1,
name: "一级部门1",
departNumber: 2,
children: [
{ id: 111, pid: 11, name: "二级部门1", departNumber: 0 },
{ id: 112, pid: 11, name: "二级部门2", departNumber: 0 },
],
},
{ id: 21, pid: 1, name: "一级部门2", departNumber: 0 },
{ id: 31, pid: 1, name: "一级部门3", departNumber: 0 },
{ id: 41, pid: 1, name: "一级部门4", departNumber: 0 },
{ id: 51, pid: 1, name: "一级部门5", departNumber: 0 },
{
id: 61,
pid: 1,
name: "一级部门6",
departNumber: 2,
children: [
{ id: 611, pid: 61, name: "二级部门3", departNumber: 0 },
{ id: 612, pid: 61, name: "二级部门4", departNumber: 0 },
],
},
],
},
];
深度优先遍历(DFS -- Depth First Search
):从根节点出发,先访问一个完整的子树,再访问相邻未被访问的子树,直到所有子树都被访问完。访问子树的操作和之前一样,直到所有节点都被访问到为止。
简单的图形化展示:
函数代码展示:
/**
* 深度优先遍历
* @params {Array} tree 树数据
* @params {Array} func 操作函数
*/
const dfsTransFn = (tree, func) => {
tree.forEach((node) => {
func(node);
// 如果子树存在,递归调用
if (node.children && node.children.length) {
dfsTransFn(node.children, func);
}
});
};
// 示例1:打印节点
dfsTransFn(tree, (node) => {
console.log(`${node.id}...${node.name}`);
});
// 示例2:树转化为列表
let treeList = [];
dfsTransFn(tree, (node) => {
treeList.push(node);
});
console.log(treeList);
函数运行效果展示:
广度优先遍历(BFS -- Breadth First Search
):从根节点出发,先访问根节点所在的初始队列,进行操作,并将该节点的所有子节点加入队列中;再依次访问队列中的第一个元素,进行操作,直到队列为空。即访问树结构的第 n+1 层前必须先访问完第 n 层,彻底搜索整个树结构,直到找到结果为止。
简单的图形化展示:
函数代码展示:
/**
* 广度优先遍历
* @params {Array} tree 树数据
* @params {Array} func 操作函数
*/
const bfsTransFn = (tree, func) => {
let node,
list = [...tree];
// shift()-取第一个;pop()-取最后一个
while ((node = list.shift())) {
func(node);
// 如果子树存在,递归调用
node.children && list.push(...node.children);
}
};
// 示例1:打印节点
bfsTransFn(tree, (node) => {
console.log(`${node.id}...${node.name}`);
});
// 示例2:树转化为列表
let treeList = [];
bfsTransFn(tree, (node) => {
treeList.push(node);
});
console.log(treeList);
函数运行效果展示:
- 深度优先:不需要记住所有节点,占用空间小;堆栈形式,先进后出
- 广度优先:需要记录所有节点,占用空间大;队列形式,先进先出
初始化时,需要请求根部门数据和一级部门数据
-
queryRootDepartDetail
:该接口用于获取根部门详情数据; -
queryChildDeparts
:该接口通过部门 id 获取直接子部门数据; -
isLeaf
:是否是叶子节点,通过是否存在子部门(o.departNumber>0
)判断
<script>
import * as R from "ramda";
// 转化函数:将接口字段转换成组件需要的数据
// 主要是添加isLeaf参数
const tranFn = (data) => {
if (R.type(data) === "Array" && R.length(data) > 0) {
return R.map(
(o) => ({
value: o.id,
key: o.id,
title: o.name,
children: [],
// 是否是叶子节点:通过是否存在子部门判断
isLeaf: !o.departNumber,
}),
data
);
} else {
return [];
}
};
export default {
created() {
this.queryAsyncDeparts();
},
methods: {
async queryAsyncDeparts() {
let res = await queryRootDepartDetail();
// 请求根部门数据
let departId = R.path(["data", "id"], res);
// 请求根部门数据
this.departSource = tranFn([res.data]);
// 默认展开根节点
this.treeExpandedKeys = [departId];
// 通过根部门ID加载子部门数据,初始化展示根部门和一级部门数据
this.queryMoreData(departId);
},
queryMoreData(departId) {},
},
};
</script>
实现效果展示:
要在对应的部门树节点添加数据,有两种方式:深度优先遍历和广度优先遍历
<script>
/**
* 异步加载数据(深度优先遍历)
* @params {Array} tree 树数据
* @params {number} departId 部门id
* @params {Array} childList 加载的数据
*/
const transTreeFn = (tree, departId, childList) => {
return R.map((o) => {
if (o.key === departId) {
o.children = tranFn(childList);
} else if (o.children && o.children.length) {
transTreeFn(o.children, departId, childList);
}
return o;
}, tree);
};
export default {
methods: {
// “加载更多”事件
onLoadData(treeNode) {
const { key } = treeNode.dataRef;
this.queryMoreData(key);
},
// 异步数据加载
queryMoreData(departId) {
// 通过部门ID,在数据源中找到对应的数据,填充子部门数据
queryChildDeparts(departId).then((res) => {
// 模拟数据
// let childList = [
// { id: 1001, name: "添加部门1", departNumber: 0 },
// { id: 1002, name: "添加部门2", departNumber: 0 },
// ];
let childList = R.pathOr([], ["data"], res);
this.departSource = transTreeFn(
this.departSource,
departId,
childList
);
});
},
// 控制展开节点
handleExpand(expandedKeys) {
this.treeExpandedKeys = expandedKeys;
},
},
};
</script>
参考文档: