Skip to content

Commit 43ec475

Browse files
committed
!22 merge xingyan/fix-tree into dev
fix(Tree): 优化性能 Created-by: xingyan Commit-by: xingyan Merged-by: GreatZP Description: fix(Tree): 优化性能 See merge request: DevCloudFE/vue-devui!22
2 parents 9dba9aa + bb48a73 commit 43ec475

File tree

9 files changed

+254
-105
lines changed

9 files changed

+254
-105
lines changed
Lines changed: 115 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { computed, ComputedRef, Ref, onUnmounted } from 'vue';
1+
import { computed, ComputedRef, Ref, onUnmounted, shallowRef, toRaw, toValue } from 'vue';
22
import { IInnerTreeNode, ITreeNode, IUseCore, valueof } from './use-tree-types';
33
import { generateInnerTree } from './utils';
44

@@ -10,8 +10,70 @@ const DEFAULT_CONFIG = {
1010
export function useCore(): (data: Ref<IInnerTreeNode[]>) => IUseCore {
1111
const nodeMap = new Map<string, IInnerTreeNode[]>();
1212
return function useCoreFn(data: Ref<IInnerTreeNode[]>): IUseCore {
13-
const getLevel = (node: IInnerTreeNode): number => {
14-
return data.value.find((item) => item.id === node.id)?.level;
13+
const hashTreeData = shallowRef<Record<string | number, IInnerTreeNode>>({});
14+
15+
const updateHashTreeData = () => {
16+
hashTreeData.value = {};
17+
for (let i = 0; i < data.value.length; i++) {
18+
const item = data.value[i];
19+
if (hashTreeData.value[item.id]) {
20+
console.error(`节点id【${item.id}】重复`);
21+
} else {
22+
hashTreeData.value[item.id] = item;
23+
}
24+
}
25+
};
26+
27+
const getInnerNode = (node: IInnerTreeNode | ITreeNode) => {
28+
if (!node) {
29+
return null;
30+
}
31+
if (node.id != undefined && hashTreeData.value[node.id]) {
32+
return hashTreeData.value[node.id];
33+
}
34+
return null;
35+
};
36+
const getLevel = (node: IInnerTreeNode) => {
37+
const innerNode = getInnerNode(node);
38+
if (innerNode) {
39+
return innerNode.level;
40+
}
41+
return undefined;
42+
};
43+
44+
const toggleChildNodeVisible = (node: IInnerTreeNode, visible: boolean) => {
45+
if (!node.childList?.length) {
46+
return;
47+
}
48+
const nodeList = [...node.childList];
49+
while (nodeList.length) {
50+
const item = nodeList.shift();
51+
if (item) {
52+
item.showNode = visible;
53+
if ((visible && item.expanded) || (!visible && item.childNodeCount)) {
54+
const temp = item.childList || [];
55+
nodeList.push(...temp);
56+
}
57+
}
58+
}
59+
};
60+
61+
const getInnerExpendedTree = (): ComputedRef<IInnerTreeNode[]> => {
62+
return computed(() => {
63+
let excludeNodes: IInnerTreeNode[] = [];
64+
const result = [];
65+
for (let i = 0, len = data?.value.length; i < len; i++) {
66+
const item = data?.value[i];
67+
if (excludeNodes.map((innerNode) => innerNode.id).includes(item.id)) {
68+
continue;
69+
}
70+
if (item.expanded !== true && !item.isLeaf) {
71+
excludeNodes = getChildren(item);
72+
}
73+
result.push(item);
74+
}
75+
return result;
76+
});
1577
};
1678

1779
const getChildren = (node: IInnerTreeNode, userConfig = DEFAULT_CONFIG): IInnerTreeNode[] => {
@@ -31,32 +93,17 @@ export function useCore(): (data: Ref<IInnerTreeNode[]>) => IUseCore {
3193
return cacheNode;
3294
}
3395
}
34-
const getInnerExpendedTree = (): ComputedRef<IInnerTreeNode[]> => {
35-
return computed(() => {
36-
let excludeNodes: IInnerTreeNode[] = [];
37-
const result = [];
38-
for (let i = 0, len = data?.value.length; i < len; i++) {
39-
const item = data?.value[i];
40-
if (excludeNodes.map((innerNode) => innerNode.id).includes(item.id)) {
41-
continue;
42-
}
43-
if (item.expanded !== true && !item.isLeaf) {
44-
excludeNodes = getChildren(item);
45-
}
46-
result.push(item);
47-
}
48-
return result;
49-
});
50-
};
96+
5197
const result = [];
5298
const config = { ...DEFAULT_CONFIG, ...userConfig };
5399
const treeData = config.expanded ? getInnerExpendedTree() : data;
54100
const startIndex = treeData.value.findIndex((item) => item.id === node.id);
101+
const nodeLevel = node.level;
55102

56-
for (let i = startIndex + 1; i < treeData.value.length && getLevel(node) < treeData.value[i].level; i++) {
103+
for (let i = startIndex + 1; i < treeData.value.length && nodeLevel < treeData.value[i].level; i++) {
57104
if (config.recursive && !treeData.value[i].isHide) {
58105
result.push(treeData.value[i]);
59-
} else if (getLevel(node) === treeData.value[i].level - 1 && !treeData.value[i].isHide) {
106+
} else if (nodeLevel === treeData.value[i].level - 1 && !treeData.value[i].isHide) {
60107
result.push(treeData.value[i]);
61108
}
62109
}
@@ -70,26 +117,43 @@ export function useCore(): (data: Ref<IInnerTreeNode[]>) => IUseCore {
70117
nodeMap.clear();
71118
};
72119

73-
const getParent = (node: IInnerTreeNode): IInnerTreeNode => {
74-
return data.value.find((item) => item.id === node.parentId);
120+
const getParent = (node: IInnerTreeNode) => {
121+
if (node.parentId !== undefined) {
122+
return hashTreeData.value[node.parentId];
123+
}
124+
return undefined;
125+
};
126+
127+
const getLastChild = (node: IInnerTreeNode) => {
128+
const children = getChildren(node, { recursive: false });
129+
const lastChild = children[children.length - 1];
130+
return lastChild?.childNodeCount ? getLastChild(lastChild) : lastChild;
75131
};
76132

133+
const getFlattenChildren = (node: IInnerTreeNode) => {
134+
const lastChild = getLastChild(node);
135+
const startPos = getIndex(node) + 1;
136+
const endPos = lastChild ? getIndex(lastChild) + 1 : startPos;
137+
return data.value.slice(startPos, endPos);
138+
};
139+
140+
let showTreeList: ComputedRef<IInnerTreeNode[]>;
141+
77142
const getExpendedTree = (): ComputedRef<IInnerTreeNode[]> => {
78-
return computed(() => {
79-
let excludeNodes: IInnerTreeNode[] = [];
80-
const result = [];
81-
for (let i = 0, len = data?.value.length; i < len; i++) {
82-
const item = data?.value[i];
83-
if (excludeNodes.map((node) => node.id).includes(item.id) || item.isHide) {
84-
continue;
85-
}
86-
if (item.expanded !== true) {
87-
excludeNodes = getChildren(item);
143+
if (showTreeList) {
144+
return showTreeList;
145+
}
146+
showTreeList = computed(() => {
147+
const res: IInnerTreeNode[] = [];
148+
const originDataVal = toValue(data);
149+
for (const item of originDataVal) {
150+
if (!item.isHide && item.showNode) {
151+
res.push(item);
88152
}
89-
result.push(item);
90153
}
91-
return result;
154+
return res;
92155
});
156+
return showTreeList;
93157
};
94158

95159
const getIndex = (node: IInnerTreeNode): number => {
@@ -100,20 +164,27 @@ export function useCore(): (data: Ref<IInnerTreeNode[]>) => IUseCore {
100164
return data.value.findIndex((item) => item.id === node.id);
101165
};
102166

103-
const getNode = (node: IInnerTreeNode): IInnerTreeNode => {
104-
return data.value.find((item) => item.id === node.id);
167+
const getNode = (node: IInnerTreeNode | string | number) => {
168+
if (typeof node === 'string' || typeof node === 'number') {
169+
return hashTreeData.value[node];
170+
} else {
171+
return getInnerNode(node) ?? undefined;
172+
}
105173
};
106174

107175
const setNodeValue = (node: IInnerTreeNode, key: keyof IInnerTreeNode, value: valueof<IInnerTreeNode>): void => {
108176
clearNodeMap();
109-
if (getIndex(node) !== -1) {
110-
data.value[getIndex(node)][key] = value;
177+
const innerNode = getInnerNode(node);
178+
if (!innerNode) {
179+
return;
111180
}
181+
innerNode[key] = value;
112182
};
113183

114184
const setTree = (newTree: ITreeNode[]): void => {
115185
clearNodeMap();
116186
data.value = generateInnerTree(newTree);
187+
updateHashTreeData();
117188
};
118189

119190
const getTree = () => {
@@ -135,6 +206,10 @@ export function useCore(): (data: Ref<IInnerTreeNode[]>) => IUseCore {
135206
setNodeValue,
136207
setTree,
137208
getTree,
209+
updateHashTreeData,
210+
toggleChildNodeVisible,
211+
hashTreeData,
212+
getFlattenChildren,
138213
};
139214
};
140215
}

packages/devui-vue/devui/tree/src/composables/use-lazy-load.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { generateInnerTree } from './utils';
44

55
export function useLazyLoad() {
66
return function useLazyLoadFn(data: Ref<IInnerTreeNode[]>, core: IUseCore, context: SetupContext): IUseLazyLoad {
7-
const { getNode, setNodeValue, getIndex, getChildren } = core;
7+
const { getNode, setNodeValue, getIndex, getChildren, updateHashTreeData, toggleChildNodeVisible } = core;
88

99
const setCommonParent = (node: IInnerTreeNode, nodes: Ref<IInnerTreeNode[]>) => {
1010
nodes.value.forEach((item) => {
@@ -21,6 +21,12 @@ export function useLazyLoad() {
2121
}
2222
};
2323

24+
// 更新childList
25+
const setChildList = (parent: IInnerTreeNode, nodes: Ref<IInnerTreeNode[]>) => {
26+
const childList = nodes.value.filter((node) => node.parentId === parent.id);
27+
parent.childList = [...childList];
28+
};
29+
2430
const dealChildNodes = (result: LazyNodeResult) => {
2531
const node = getNode(result.node);
2632
setNodeValue(node, 'loading', false);
@@ -29,9 +35,15 @@ export function useLazyLoad() {
2935
setCommonParent(node, childNodes);
3036
// 插入children
3137
insertChildrenNodes(node, childNodes);
38+
// 更新hashTreeData
39+
updateHashTreeData();
3240
// 更新childrenNodes数量
3341
const childrenNodes = getChildren(node);
3442
setNodeValue(node, 'childNodeCount', childrenNodes.length);
43+
// 更新childList
44+
setChildList(node, childNodes);
45+
// 更新子节点展开状态
46+
toggleChildNodeVisible(node, true);
3547
};
3648

3749
const lazyLoadNodes = (node: IInnerTreeNode): void => {

packages/devui-vue/devui/tree/src/composables/use-operate.ts

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { Ref, ref } from 'vue';
1+
import { Ref, ref, toRaw } from 'vue';
22
import { randomId } from '../../../shared/utils';
33
import { IInnerTreeNode, ITreeNode, IUseCore, IUseOperate } from './use-tree-types';
44

55
export function useOperate() {
66
return function useOperateFn(data: Ref<IInnerTreeNode[]>, core: IUseCore): IUseOperate {
7-
const { setNodeValue, getChildren, getIndex, getLevel, getParent } = core;
7+
const { setNodeValue, getChildren, getIndex, getLevel, getParent, updateHashTreeData, getFlattenChildren } = core;
88

9-
const insertBefore = (parentNode: ITreeNode, node: ITreeNode, referenceNode?: ITreeNode): void => {
9+
const insertBefore = (parentNode: IInnerTreeNode, node: IInnerTreeNode, referenceNode?: IInnerTreeNode): void => {
1010
const children = getChildren(parentNode, {
1111
recursive: false,
1212
});
@@ -36,6 +36,7 @@ export function useOperate() {
3636
level: getLevel(parentNode) + 1,
3737
parentId: parentNode.id,
3838
isLeaf: true,
39+
showNode: true,
3940
parentChildNodeCount: children.length + 1,
4041
currentIndex: lastChild && typeof lastChild.currentIndex === 'number' ? lastChild.currentIndex + 1 : 0,
4142
});
@@ -44,7 +45,18 @@ export function useOperate() {
4445
currentNode.value.id = randomId();
4546
}
4647

48+
parentNode.childList = [];
4749
data.value = data.value.slice(0, insertedIndex).concat(currentNode.value, data.value.slice(insertedIndex, data.value.length));
50+
data.value.forEach((item) => {
51+
if (item.parentId === parentNode.id) {
52+
setNodeValue(item, 'parentChildNodeCount', children.length + 1);
53+
parentNode.childList?.push(toRaw(item));
54+
}
55+
});
56+
parentNode.childList.forEach((v, i) => (v.currentIndex = i));
57+
parentNode.children = parentNode.childList;
58+
parentNode.isLeaf = !parentNode.children.length;
59+
updateHashTreeData();
4860
};
4961

5062
const removeNode = (node: IInnerTreeNode, config = { recursive: true }): void => {
@@ -54,23 +66,28 @@ export function useOperate() {
5466
});
5567
}
5668

57-
data.value = data.value.filter((item) => {
58-
if (config.recursive) {
59-
return (
60-
item.id !== node.id &&
61-
!getChildren(node)
62-
.map((nodeItem) => nodeItem.id)
63-
.includes(item.id)
64-
);
69+
const children = getFlattenChildren(node);
70+
const removeIds = [node.id, ...children.map((v) => v.id)];
71+
data.value = data.value.filter((item) => !removeIds.includes(item.id));
72+
73+
const parentNode = getParent(node);
74+
const originParentNode = toRaw(parentNode);
75+
if (parentNode && originParentNode) {
76+
const parentChildren = getChildren(parentNode);
77+
if (parentChildren?.length) {
78+
const list = originParentNode.childList || [];
79+
parentNode.childList = list.filter((item) => item.id !== node.id) ?? [];
80+
setNodeValue(parentNode, 'childNodeCount', list.length);
6581
} else {
66-
return item.id !== node.id;
82+
parentNode.childList = [];
83+
setNodeValue(parentNode, 'childNodeCount', 0);
84+
setNodeValue(parentNode, 'isLeaf', true);
85+
setNodeValue(parentNode, 'expanded', false);
6786
}
68-
});
69-
70-
// 子节点全部删完了,应该设置父节点为叶子结点(isLeaf)
71-
if (getParent(node) && getChildren(getParent(node)).length === 0) {
72-
setNodeValue(getParent(node), 'isLeaf', true);
87+
parentNode.children = originParentNode.children;
7388
}
89+
90+
updateHashTreeData();
7491
};
7592

7693
const editNode = (node: IInnerTreeNode, label: string): void => {

0 commit comments

Comments
 (0)