Fix slow appearance of nodes on tree view
The tree view rendering performance is optimized by improving the node render queue ordering. The node rendering order is modified based on the expansion state and the depth in the hierarchy, leading to faster rendering of visible nodes. This optimization is applied when the tree nodes are not expanded to improve the rendering speed. This new ordering ensures that nodes are rendered more efficiently, prioritizing nodes that are collapsed and are at a higher level in the hierarchy.
This commit is contained in:
@@ -51,7 +51,7 @@ import {
|
||||
} from 'vue';
|
||||
import { TreeRoot } from '../TreeRoot/TreeRoot';
|
||||
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
||||
import { NodeRenderingStrategy } from '../Rendering/NodeRenderingStrategy';
|
||||
import { NodeRenderingStrategy } from '../Rendering/Scheduling/NodeRenderingStrategy';
|
||||
import { useNodeState } from './UseNodeState';
|
||||
import { TreeNode } from './TreeNode';
|
||||
import LeafTreeNode from './LeafTreeNode.vue';
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ReadOnlyTreeNode } from '../../Node/TreeNode';
|
||||
import { RenderQueueOrderer } from './RenderQueueOrderer';
|
||||
|
||||
export class CollapseDepthOrderer implements RenderQueueOrderer {
|
||||
public orderNodes(nodes: Iterable<ReadOnlyTreeNode>): ReadOnlyTreeNode[] {
|
||||
return orderNodes(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
function orderNodes(nodes: Iterable<ReadOnlyTreeNode>): ReadOnlyTreeNode[] {
|
||||
return [...nodes]
|
||||
.sort((a, b) => {
|
||||
const [aCollapseStatus, bCollapseStatus] = [isNodeCollapsed(a), isNodeCollapsed(b)];
|
||||
if (aCollapseStatus !== bCollapseStatus) {
|
||||
return (aCollapseStatus ? 1 : 0) - (bCollapseStatus ? 1 : 0);
|
||||
}
|
||||
return a.hierarchy.depthInTree - b.hierarchy.depthInTree;
|
||||
});
|
||||
}
|
||||
|
||||
function isNodeCollapsed(node: ReadOnlyTreeNode): boolean {
|
||||
if (!node.state.current.isExpanded) {
|
||||
return true;
|
||||
}
|
||||
if (node.hierarchy.parent) {
|
||||
return isNodeCollapsed(node.hierarchy.parent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ReadOnlyTreeNode } from '../../Node/TreeNode';
|
||||
|
||||
export interface RenderQueueOrderer {
|
||||
orderNodes(nodes: Iterable<ReadOnlyTreeNode>): ReadOnlyTreeNode[];
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TreeNode } from '../Node/TreeNode';
|
||||
import { TreeNode } from '../../Node/TreeNode';
|
||||
|
||||
export interface NodeRenderingStrategy {
|
||||
shouldRender(node: TreeNode): boolean;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DelayScheduler } from './DelayScheduler';
|
||||
import { DelayScheduler } from '../DelayScheduler';
|
||||
|
||||
export class TimeoutDelayScheduler implements DelayScheduler {
|
||||
private timeoutId: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||
@@ -1,13 +1,15 @@
|
||||
import {
|
||||
WatchSource, computed, shallowRef, triggerRef, watch,
|
||||
WatchSource, shallowRef, triggerRef, watch,
|
||||
} from 'vue';
|
||||
import { ReadOnlyTreeNode } from '../Node/TreeNode';
|
||||
import { useNodeStateChangeAggregator } from '../UseNodeStateChangeAggregator';
|
||||
import { TreeRoot } from '../TreeRoot/TreeRoot';
|
||||
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
||||
import { NodeRenderingStrategy } from './NodeRenderingStrategy';
|
||||
import { NodeRenderingStrategy } from './Scheduling/NodeRenderingStrategy';
|
||||
import { DelayScheduler } from './DelayScheduler';
|
||||
import { TimeoutDelayScheduler } from './TimeoutDelayScheduler';
|
||||
import { TimeoutDelayScheduler } from './Scheduling/TimeoutDelayScheduler';
|
||||
import { RenderQueueOrderer } from './Ordering/RenderQueueOrderer';
|
||||
import { CollapseDepthOrderer } from './Ordering/CollapseDepthOrderer';
|
||||
|
||||
/**
|
||||
* Renders tree nodes gradually to prevent UI freeze when loading large amounts of nodes.
|
||||
@@ -19,6 +21,7 @@ export function useGradualNodeRendering(
|
||||
scheduler: DelayScheduler = new TimeoutDelayScheduler(),
|
||||
initialBatchSize = 30,
|
||||
subsequentBatchSize = 5,
|
||||
orderer: RenderQueueOrderer = new CollapseDepthOrderer(),
|
||||
): NodeRenderingStrategy {
|
||||
const nodesToRender = new Set<ReadOnlyTreeNode>();
|
||||
const nodesBeingRendered = shallowRef(new Set<ReadOnlyTreeNode>());
|
||||
@@ -28,8 +31,6 @@ export function useGradualNodeRendering(
|
||||
const { onNodeStateChange } = useChangeAggregator(treeWatcher);
|
||||
const { nodes } = useTreeNodes(treeWatcher);
|
||||
|
||||
const orderedNodes = computed<readonly ReadOnlyTreeNode[]>(() => nodes.value.flattenedNodes);
|
||||
|
||||
function updateNodeRenderQueue(node: ReadOnlyTreeNode, isVisible: boolean) {
|
||||
if (isVisible
|
||||
&& !nodesToRender.has(node)
|
||||
@@ -47,14 +48,15 @@ export function useGradualNodeRendering(
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => orderedNodes.value, (newNodes) => {
|
||||
watch(() => nodes.value, (newNodes) => {
|
||||
nodesToRender.clear();
|
||||
nodesBeingRendered.value.clear();
|
||||
if (!newNodes?.length) {
|
||||
if (!newNodes || newNodes.flattenedNodes.length === 0) {
|
||||
triggerRef(nodesBeingRendered);
|
||||
return;
|
||||
}
|
||||
newNodes
|
||||
.flattenedNodes
|
||||
.filter((node) => node.state.current.isVisible)
|
||||
.forEach((node) => nodesToRender.add(node));
|
||||
beginRendering();
|
||||
@@ -80,10 +82,8 @@ export function useGradualNodeRendering(
|
||||
return;
|
||||
}
|
||||
isRenderingInProgress = true;
|
||||
const sortedNodes = Array.from(nodesToRender).sort(
|
||||
(a, b) => orderedNodes.value.indexOf(a) - orderedNodes.value.indexOf(b),
|
||||
);
|
||||
const currentBatch = sortedNodes.slice(0, batchSize);
|
||||
const orderedNodes = orderer.orderNodes(nodesToRender);
|
||||
const currentBatch = orderedNodes.slice(0, batchSize);
|
||||
if (currentBatch.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from 'vue';
|
||||
import HierarchicalTreeNode from '../Node/HierarchicalTreeNode.vue';
|
||||
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
|
||||
import { NodeRenderingStrategy } from '../Rendering/NodeRenderingStrategy';
|
||||
import { NodeRenderingStrategy } from '../Rendering/Scheduling/NodeRenderingStrategy';
|
||||
import { TreeRoot } from './TreeRoot';
|
||||
|
||||
export default defineComponent({
|
||||
|
||||
Reference in New Issue
Block a user