How to Work with the Element UI Tree Component for Hierarchical Data Display and Operations
The Element UI Tree component (ElTree) renders hierarchical data recursively using ElTreeNode for individual items and TreeStore for state management, supporting lazy loading, checkboxes, drag-and-drop, and comprehensive programmatic APIs.
The ElemeFE/element repository provides a robust Tree component for Vue.js applications requiring nested data visualization. This component handles complex hierarchical relationships through a clean store-pattern architecture that separates presentation logic from data manipulation. Whether building file browsers, organization charts, or nested category trees, understanding the internal structure of ElTree, ElTreeNode, and TreeStore is essential for effective implementation.
Architecture of the Tree Component
The Tree component follows a three-layer architecture where data flows from properties through a central store into recursive node components.
Core Components
ElTree (packages/tree/src/tree.vue) serves as the root container. It accepts the data prop, instantiates a TreeStore (lines 25-38), and exposes public methods including append, remove, filter, and getCheckedKeys. This component handles drag-and-drop event coordination, keyboard navigation via handleKeydown (lines 94-119), and emits tree-level events.
ElTreeNode (packages/tree/src/tree-node.vue) renders individual nodes recursively. Each instance manages its own expansion state, checkbox visibility, and drag handles, emitting node-specific events like node-click and node-expand that bubble up to the root component.
TreeStore (packages/tree/src/model/tree-store.js) acts as the pure data layer. It maintains the canonical node hierarchy, manages selection states (checkedKeys, halfCheckedKeys), and provides all mutation methods. Every structural change flows through this store, which then notifies the UI layer to re-render.
Data Flow
-
Props → Store: On creation,
ElTreeinitializesTreeStorewithdata,props(field mappings),nodeKey, andlazyconfiguration. -
Store → Nodes: The store creates a virtual root node (
this.root). The template rendersroot.childNodesusing recursive<el-tree-node>components (lines 13-21 oftree.vue). -
Interaction → Store: User actions (clicks, checks, drops) or programmatic API calls forward to store methods like
setChecked,remove, orinsertBefore. The store updates its internal state and triggers Vue's reactivity system.
Core Features and Implementation Details
Lazy Loading for Large Hierarchies
When lazy is set to true, expanding a node triggers the load prop function. This function receives the node being expanded and a resolve callback. Once asynchronous data fetching completes, calling resolve(children) registers the new nodes in the store without reloading the entire tree. This implementation in tree-node.vue prevents memory issues with massive datasets.
Checkbox Selection and State Management
The show-checkbox prop enables checkboxes on each node. The store maintains three selection states: checked, unchecked, and indeterminate (half-checked). By default, checking a parent checks all descendants unless check-strictly is enabled. Access selection programmatically via getCheckedKeys(), getCheckedNodes(), setCheckedKeys(), or setChecked().
Drag-and-Drop with Custom Constraints
Enable native HTML5 drag-and-drop by setting draggable. The component emits node-drag-start, node-drag-over, and node-drag-end events. Customize behavior using:
allow-drag: Function determining if a specific node can be draggedallow-drop: Function determining if a dragged node can be dropped at a specific position (before, after, or inner)
Filtering and Search
Call this.$refs.tree.filter(value) to filter the tree. This delegates to store.filter, which invokes the filterNodeMethod prop function for every node. Return true to show the node, false to hide it. The filter automatically handles parent visibility when children match.
Keyboard Navigation
The handleKeydown method (lines 94-119 of tree.vue) implements full keyboard accessibility:
- Arrow keys navigate between nodes
- Space or Enter toggles checkboxes
- Left/Right arrows collapse and expand nodes
Practical Implementation Examples
Rendering a Basic Static Tree
<template>
<el-tree
:data="treeData"
:props="{ children: 'children', label: 'name' }"
:default-expand-all="true"
@node-click="onNodeClick">
</el-tree>
</template>
<script>
export default {
data() {
return {
treeData: [
{
name: 'Folder A',
children: [{ name: 'File A1' }, { name: 'File A2' }]
},
{
name: 'Folder B',
children: [{ name: 'File B1' }]
}
]
};
},
methods: {
onNodeClick(nodeData, node, component) {
console.log('Clicked:', nodeData);
}
}
};
</script>
Key points:
- The
:propsobject maps your data fields to the component's expectedchildrenandlabelproperties default-expand-allexpands all nodes on mountnode-clickemits the raw data, the internal node object, and the Vue component instance
Implementing Lazy Loading for Large Datasets
<template>
<el-tree
:data="treeData"
:load="loadNode"
lazy
:props="{ children: 'children', label: 'title' }">
</el-tree>
</template>
<script>
export default {
data() {
return {
treeData: [{ id: 1, title: 'Root', hasChildren: true }]
};
},
methods: {
loadNode(node, resolve) {
if (node.level === 0) return resolve(this.treeData);
// Simulate async API call
setTimeout(() => {
const children = [
{ id: node.data.id * 10 + 1, title: `Child of ${node.data.title}` }
];
resolve(children);
}, 500);
}
}
};
</script>
Implementation notes:
- Both
lazyprop andloadfunction are required - The
resolvecallback must be called with an array of child nodes - Leaf nodes are automatically detected when
resolve([])is called with an empty array
Managing Node Selection and Checkboxes
<template>
<div>
<el-tree
:data="treeData"
show-checkbox
node-key="id"
:default-checked-keys="[2, 3]"
:check-strictly="true"
ref="myTree">
</el-tree>
<button @click="logChecked">Get Checked Keys</button>
</div>
</template>
<script>
export default {
data() {
return {
treeData: [
{ id: 1, label: 'Parent', children: [
{ id: 2, label: 'Child 1' },
{ id: 3, label: 'Child 2' }
]}
]
};
},
methods: {
logChecked() {
// Returns array of node-key values (e.g., [2, 3])
const keys = this.$refs.myTree.getCheckedKeys();
console.log('Checked keys:', keys);
// Returns array of node data objects
const nodes = this.$refs.myTree.getCheckedNodes();
console.log('Checked nodes:', nodes);
}
}
};
</script>
Important considerations:
node-keyis mandatory for programmatic selection methods; it specifies which property serves as the unique identifiercheck-strictlydisables the default behavior where parent and child selections are linked- Use
setCheckedKeys([1, 2])to programmatically set selection
Enabling Drag-and-Drop with Custom Constraints
<template>
<el-tree
:data="treeData"
draggable
:allow-drag="canDrag"
:allow-drop="canDrop"
@node-drop="onDrop"
node-key="id">
</el-tree>
</template>
<script>
export default {
data() {
return {
treeData: [
{ id: 1, label: 'Root', children: [
{ id: 2, label: 'Draggable Node' },
{ id: 3, label: 'Drop Target', children: [] }
]}
]
};
},
methods: {
canDrag(node) {
// Prevent dragging nodes marked as disabled
return !node.disabled;
},
canDrop(dragNode, dropNode, type) {
// Type can be: 'prev' (before), 'next' (after), or 'inner' (child)
// Prevent dropping into leaf nodes (only allow dropping before/after)
if (type === 'inner' && dropNode.isLeaf) return false;
return true;
},
onDrop(dragNode, dropNode, type, ev) {
console.log(`Moved ${dragNode.data.label} ${type} ${dropNode.data.label}`);
// Trigger backend sync here
}
}
};
</script>
Drag behavior details:
allow-dragreceives the node object and must return a booleanallow-dropreceives the dragged node, target node, and drop position string- The
node-dropevent fires after the internal state updates successfully
Programmatic Tree Manipulation
<template>
<div>
<el-tree :data="treeData" node-key="id" ref="tree"></el-tree>
<button @click="addNode">Add Child to Node 5</button>
<button @click="removeNode">Remove Node 3</button>
</div>
</template>
<script>
export default {
data() {
return {
treeData: [
{ id: 1, label: 'Root', children: [
{ id: 3, label: 'Node 3' },
{ id: 5, label: 'Node 5', children: [] }
]}
]
};
},
methods: {
addNode() {
// Append requires parent node reference, obtained via getNode(key)
const parentNode = this.$refs.tree.getNode(5);
this.$refs.tree.append({ id: 99, label: 'New Node' }, parentNode);
},
removeNode() {
// Remove by key using getNode to resolve the node object
this.$refs.tree.remove(this.$refs.tree.getNode(3));
}
}
};
</script>
API methods available:
append(data, parentNode)andappend(data, parentKey): Add child to endinsertBefore(data, refNode)andinsertAfter(data, refNode): Insert at specific positionremove(node): Delete node and all childrengetNode(key): Retrieve internal node object by its key value
Key Source Files and References
Understanding the source structure enables advanced customization and debugging:
packages/tree/src/tree.vue: Root component implementing the public API, event handling, and store initialization (lines 25-38 for store creation, lines 60+ for methods likeappendandremove)packages/tree/src/tree-node.vue: Recursive node renderer handling expand/collapse icons, checkboxes, and drag event emissionpackages/tree/src/model/tree-store.js: Core data model containingsetChecked,filter,registerNode, and all mutation logicpackages/theme-chalk/src/tree.scss: Styling for indentation guides, drag indicators, and empty statesexamples/docs/en-US/tree.md: Official documentation with complete props, events, and methods referencetest/unit/specs/tree.spec.js: Unit tests demonstrating edge cases for lazy loading, checking, and drag-and-drop
Summary
- Store-pattern architecture separates
ElTree(UI),ElTreeNode(recursive rendering), andTreeStore(data manipulation) for maintainable hierarchical data management - Lazy loading requires both the
lazyprop and aloadfunction that callsresolve(children)to populate nodes on demand without loading the entire dataset - Programmatic operations depend on the
node-keyprop to uniquely identify nodes for methods likegetNode,append,remove, andsetCheckedKeys - Drag-and-drop uses native HTML5 events with
allow-dragandallow-dropcallbacks to implement business logic constraints - Filtering leverages
filterNodeMethodto determine visibility, called via thefilter(value)method on the component instance
Frequently Asked Questions
How does the Tree component handle large datasets efficiently?
The Tree component implements lazy loading through the lazy prop and load function. When enabled, child nodes load only when a parent expands, preventing memory and performance issues with massive trees. The TreeStore maintains only loaded nodes in memory, and the load function receives a resolve callback to register new children asynchronously without re-rendering the entire tree.
What is the difference between node-key and the standard key prop?
The node-key prop specifies which property in your data objects serves as the unique identifier (e.g., id or uuid). This is required for all programmatic operations like getNode, setCheckedKeys, and remove. The standard Vue key prop is used internally for list rendering optimization, while node-key is specific to the Tree component's data model and API methods.
How can I filter or search nodes in the Tree component?
Call this.$refs.tree.filter(searchValue) where searchValue is passed to your filterNodeMethod function. This function receives two arguments: value (the search string) and data (the node's data object), and must return boolean to determine visibility. The filter automatically handles parent visibility when children match, ensuring the tree structure remains intact while hiding non-matching branches.
Is drag-and-drop supported between different Tree instances?
No, the Element UI Tree component does not support cross-tree dragging natively. The drag event handlers in tree-node.vue scope drag operations to a single ElTree instance. To implement cross-tree dragging, you would need to manually coordinate between instances using the node-drag-start, node-drag-end, and node-drop events, manually removing nodes from the source tree and appending them to the destination tree using the programmatic API.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →