# How to Work with the Element UI Tree Component for Hierarchical Data Display and Operations

> Master the Element UI Tree component for displaying hierarchical data. Learn about lazy loading, checkboxes, drag-and-drop, and programmatic control for efficient data management.

- Repository: [饿了么前端/element](https://github.com/ElemeFE/element)
- Tags: how-to-guide
- Published: 2026-03-07

---

**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`](https://github.com/ElemeFE/element/blob/main/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`](https://github.com/ElemeFE/element/blob/main/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`](https://github.com/ElemeFE/element/blob/main/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

1. **Props → Store**: On creation, `ElTree` initializes `TreeStore` with `data`, `props` (field mappings), `nodeKey`, and `lazy` configuration.

2. **Store → Nodes**: The store creates a virtual root node (`this.root`). The template renders `root.childNodes` using recursive `<el-tree-node>` components (lines 13-21 of [`tree.vue`](https://github.com/ElemeFE/element/blob/main/tree.vue)).

3. **Interaction → Store**: User actions (clicks, checks, drops) or programmatic API calls forward to store methods like `setChecked`, `remove`, or `insertBefore`. 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`](https://github.com/ElemeFE/element/blob/main/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 dragged
- **`allow-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`](https://github.com/ElemeFE/element/blob/main/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

```vue
<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 `:props` object maps your data fields to the component's expected `children` and `label` properties
- `default-expand-all` expands all nodes on mount
- `node-click` emits the raw data, the internal node object, and the Vue component instance

### Implementing Lazy Loading for Large Datasets

```vue
<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 `lazy` prop and `load` function are required
- The `resolve` callback 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

```vue
<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-key` is mandatory for programmatic selection methods; it specifies which property serves as the unique identifier
- `check-strictly` disables 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

```vue
<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-drag` receives the node object and must return a boolean
- `allow-drop` receives the dragged node, target node, and drop position string
- The `node-drop` event fires after the internal state updates successfully

### Programmatic Tree Manipulation

```vue
<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)` and `append(data, parentKey)`: Add child to end
- `insertBefore(data, refNode)` and `insertAfter(data, refNode)`: Insert at specific position
- `remove(node)`: Delete node and all children
- `getNode(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`](https://github.com/ElemeFE/element/blob/main/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 like `append` and `remove`)
- **[`packages/tree/src/tree-node.vue`](https://github.com/ElemeFE/element/blob/main/packages/tree/src/tree-node.vue)**: Recursive node renderer handling expand/collapse icons, checkboxes, and drag event emission
- **[`packages/tree/src/model/tree-store.js`](https://github.com/ElemeFE/element/blob/main/packages/tree/src/model/tree-store.js)**: Core data model containing `setChecked`, `filter`, `registerNode`, and all mutation logic
- **[`packages/theme-chalk/src/tree.scss`](https://github.com/ElemeFE/element/blob/main/packages/theme-chalk/src/tree.scss)**: Styling for indentation guides, drag indicators, and empty states
- **[`examples/docs/en-US/tree.md`](https://github.com/ElemeFE/element/blob/main/examples/docs/en-US/tree.md)**: Official documentation with complete props, events, and methods reference
- **[`test/unit/specs/tree.spec.js`](https://github.com/ElemeFE/element/blob/main/test/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), and `TreeStore` (data manipulation) for maintainable hierarchical data management
- **Lazy loading** requires both the `lazy` prop and a `load` function that calls `resolve(children)` to populate nodes on demand without loading the entire dataset
- **Programmatic operations** depend on the `node-key` prop to uniquely identify nodes for methods like `getNode`, `append`, `remove`, and `setCheckedKeys`
- **Drag-and-drop** uses native HTML5 events with `allow-drag` and `allow-drop` callbacks to implement business logic constraints
- **Filtering** leverages `filterNodeMethod` to determine visibility, called via the `filter(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`](https://github.com/ElemeFE/element/blob/main/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.