需求
实现树形数据懒加载,需要新增、删除等功能,数据新增删除后,能够保持子目录及以下目录的数据存在
问题
目前antd组件库提供的 tree组件使用 loadData api来实现树形数据的懒加载,但是涉及数据的删除和新增,就会导致子级及以下的目录数据缺失(原因是新获取的数据没有子集数据),还需要处理loadedKeys(已加载的目录)等,才能重新加载新的子数据,过程繁琐。
解决方式
利用展开目录的事件api (onExpand),实现获取新的数据,与原有数据进行匹配,保留对应的旧数据
代码
import { useState, useEffect } from "react";
import { Tree } from "antd";
import { DownOutlined } from "@ant-design/icons";
import { isEmpty } from "lodash";
interface DataNode {
title: string;
key: string;
parentId?: string | null;
isLeaf?: boolean;
children?: DataNode[];
}
const initTreeData: DataNode[] = [
{ title: "Expand to load", key: "0", isLeaf: false, parentId: null },
{ title: "Expand to load", key: "1", isLeaf: true, parentId: null },
{ title: "Tree Node", key: "2", isLeaf: true, parentId: null },
];
const childTreeData: DataNode[] = [
{ title: "Expand to load", key: "0-0", isLeaf: true, parentId: "0" },
{ title: "Expand to load", key: "0-1", isLeaf: true, parentId: "0" },
{ title: "Tree Node", key: "0-2", isLeaf: true, parentId: "0" },
];
const App = () => {
const [treeData, setTreeData] = useState<DataNode[]>([]);
// 拼接数据
const concatOldItem = (oldData: DataNode[], newData: DataNode[]) => {
return newData.map((item) => {
const oldItemArr = oldData.filter((oldItem) => oldItem.key === item.key);
if (!isEmpty(oldItemArr)) {
item.children = oldItemArr[0].children;
}
return item;
});
};
const getTreeFirst = () => {
// 首次只加载一级目录
Promise.resolve(initTreeData).then((res) => {
// 新增一级目录时,获取原有的children数据
setTreeData((origin) => {
if (isEmpty(origin)) {
return res;
} else {
return concatOldItem(origin, res);
}
});
});
};
useEffect(() => {
getTreeFirst();
}, []);
const updateTreeData = (
list: DataNode[],
key: React.Key,
children: DataNode[]
): DataNode[] => {
return list.map((node: DataNode) => {
if (node.key === key) {
// 新数据从旧数据中过滤获取,否则子数据会出现空白,得重新加载
const oldChild = node.children;
let newChild;
if (oldChild) {
newChild = concatOldItem(oldChild, children);
}
return {
...node,
children: newChild || children,
};
}
if (node.children) {
return {
...node,
children: updateTreeData(node.children, key, children),
};
}
return node;
});
};
const onLoadData = (node: DataNode) => {
// 实际项目根据key获取子数据,key是每一项的id赋值
const { key } = node;
Promise.resolve(childTreeData).then((res) => {
// 新增一级目录时,获取原有的children数据
setTreeData((origin) => {
return updateTreeData(origin, key, res);
});
});
};
const onHandleExpand = (expanded: boolean, node: DataNode) => {
if (expanded && !node.children) {
onLoadData(node);
}
};
// 删除
const handleDelete = (node: DataNode) => {
Promise.resolve().then((res) => {
// 删除成功更新当前
if (node?.parentId) {
onLoadData({ key: node.parentId } as DataNode);
} else {
// 删除一级菜单
getTreeFirst();
}
});
};
// 新增
const handleAdd = (node: DataNode) => {
Promise.resolve().then((res) => {
//新增成功更新当前
if (node?.parentId) {
onLoadData({ key: node?.parentId } as DataNode);
} else {
// 新增一级菜单
getTreeFirst();
}
});
};
return (
<Tree
showLine={{ showLeafIcon: false }}
switcherIcon={<DownOutlined />}
blockNode
treeData={treeData}
onExpand={(expandKeys, { expanded, node }) => {
// 利用展开目录实行懒加载数据
onHandleExpand(expanded, node as unknown as DataNode);
}}
onSelect={(selectedKeys, { selected, selectedNodes }) => {
// 节点选中
console.log(selectedKeys, selected, selectedNodes);
// const key = selectedKeys[0] as string;
// if (selectedNodes[0]?.isLeaf) {
// // 只选中叶节点,不选择目录
// onSelect(key);
// }
}}
/>
);
};
export default App;