> Leaf UI 0.8.1 · Package: @ifhover/leaf-ui · Source: 435bab23cad7d343653d9b63b049c0b0601eeefb
> Browser documentation: https://ifhover.github.io/leaf-ui/components/tree.html

> For AI agents: the complete documentation index is available at https://ifhover.github.io/leaf-ui/llms.txt, the full documentation bundle is available at https://ifhover.github.io/leaf-ui/llms-full.txt.

# Tree 树

按层级浏览、选择或勾选内容。

## 浏览与选择


 

设计资源

 

组件规范

 

品牌颜色

 

归档资源（不可选）

 

研发项目

已选: 暂无

basic.tsx

复制代码

```tsx lineNumbers file="../../src/examples/tree/basic.tsx"
import { Space, Text, Tree } from '@ifhover/leaf-ui';

import { useState } from 'react';

import { treeData } from './data';

export function TreeBasic() {
  const [selected, setSelected] = useState<readonly string[]>([]);
  return (
    <Space direction="vertical" align="stretch">
      <Tree
        data={treeData}
        defaultExpandedKeys={['design']}
        showIcon
        selectedKeys={selected}
        onSelect={setSelected}
      />
      <output>
        <Text tone="muted">已选: {selected.join(', ') || '暂无'}</Text>
      </output>
    </Space>
  );
}
```

展开代码

## 父子勾选联动


 

设计资源

 

组件规范

 

品牌颜色

 

归档资源（不可选）

 

研发项目

 

网站

 

移动应用

勾选节点: components

checkable.tsx

复制代码

```tsx lineNumbers file="../../src/examples/tree/checkable.tsx"
import { Space, Text, Tree } from '@ifhover/leaf-ui';

import { useState } from 'react';

import { treeData } from './data';

export function TreeCheckable() {
  const [checked, setChecked] = useState<readonly string[]>(['components']);
  return (
    <Space direction="vertical" align="stretch">
      <Tree
        data={treeData}
        checkable
        selectable={false}
        defaultExpandAll
        showLine
        checkedKeys={checked}
        onCheck={setChecked}
      />
      <output>
        <Text tone="muted">勾选节点: {checked.join(', ') || '暂无'}</Text>
      </output>
    </Space>
  );
}
```

展开代码

## 独立勾选

父节点和子节点分别勾选，互不影响。


 

设计资源

 

组件规范

 

品牌颜色

 

归档资源（不可选）

 

研发项目

 

网站

 

移动应用

strict.tsx

复制代码

```tsx lineNumbers file="../../src/examples/tree/strict.tsx"
import { Tree } from '@ifhover/leaf-ui';

import { treeData } from './data';

export function TreeStrict() {
  return <Tree data={treeData} checkable checkStrictly selectable={false} defaultExpandAll />;
}
```

展开代码

## 搜索节点


 

设计资源

 

研发项目

search.tsx

复制代码

```tsx lineNumbers file="../../src/examples/tree/search.tsx"
import { Input, Space, Tree } from '@ifhover/leaf-ui';

import { Search } from 'lucide-react';

import { useState } from 'react';

import { treeData } from './data';

export function TreeSearch() {
  const [query, setQuery] = useState('');
  return (
    <Space direction="vertical" align="stretch">
      <Input
        aria-label="搜索节点"
        placeholder="搜索节点"
        prefix={<Search />}
        value={query}
        onChange={(event) => setQuery(event.target.value)}
        allowClear
      />
      <Tree data={treeData} searchValue={query} showIcon />
    </Space>
  );
}
```

展开代码

## 扩展用法

通过手柄拖动节点，移到目标行的上方、中央或下方，分别放在目标之前、内部或之后。键盘操作时，空格开始或放置，上下选择目标，左/右选择前方/后方，Escape 取消。`onDrop` 提供新数据，由调用方更新 `data`。


展开项目加载子项，通过手柄移动节点。

 

项目

 

归档

10000 个节点，仅挂载可视行。

 

节点 1

 

节点 2

 

节点 3

 

节点 4

 

节点 5

 

节点 6

 

节点 7

 

节点 8

 

节点 9

 

节点 10

 

节点 11

extensions.tsx

复制代码

```tsx lineNumbers file="../../src/examples/tree/extensions.tsx"
import type { TreeNode } from '@ifhover/leaf-ui';

import { Space, Tree } from '@ifhover/leaf-ui';

import { useState } from 'react';

const initial: TreeNode[] = [
  { key: 'projects', title: '项目', isLeaf: false },
  { key: 'archive', title: '归档', children: [{ key: 'draft', title: '草稿', isLeaf: true }] },
];

export function ExtensionDemo() {
  const [data, setData] = useState(initial);
  return (
    <Space direction="vertical" align="stretch">
      <p>展开项目加载子项，通过手柄移动节点。</p>
      <Tree
        data={data}
        loadData={async (node, signal) => {
          await new Promise<void>((resolve, reject) => {
            const timer = setTimeout(resolve, 500);
            signal.addEventListener(
              'abort',
              () => {
                clearTimeout(timer);
                reject(new DOMException('Aborted', 'AbortError'));
              },
              { once: true },
            );
          });
          return [
            { key: `${node.key}-design`, title: '设计', isLeaf: true },
            { key: `${node.key}-dev`, title: '开发', isLeaf: true },
          ];
        }}
        draggable
        onDrop={(info) => setData(info.data)}
      />
      <p>10000 个节点，仅挂载可视行。</p>
      <Tree
        virtual
        height={240}
        data={Array.from({ length: 10000 }, (_, i) => ({
          key: String(i),
          title: `节点 ${i + 1}`,
        }))}
      />
    </Space>
  );
}
```

展开代码

## 刷新异步数据

更新 `cacheKey` 可以刷新同一组节点的懒加载缓存。更换 `data` 或 `loadData` 也会清除缓存；保持它们的引用稳定，避免因无关渲染重复加载。过期请求被取消后，不会覆盖新数据。


刷新数据

 

文档

根节点 key 保持不变，数据版本变化后会清除旧子节点并取消未完成的请求。

capabilities.tsx

复制代码

```tsx lineNumbers file="../../src/examples/tree/capabilities.tsx"
import { Button, Space, Text, Tree, type TreeNode } from '@ifhover/leaf-ui';

import { useCallback, useMemo, useState } from 'react';

export function TreeCacheRefresh() {
  const [version, setVersion] = useState(1);
  const data = useMemo<readonly TreeNode[]>(
    () => [{ key: 'root', title: '文档', isLeaf: false }],
    [false],
  );
  const loadData = useCallback(
    async (node: TreeNode, signal: AbortSignal): Promise<readonly TreeNode[]> => {
      await new Promise<void>((resolve) => {
        const timer = setTimeout(resolve, 400);
        signal.addEventListener(
          'abort',
          () => {
            clearTimeout(timer);
            resolve();
          },
          { once: true },
        );
      });
      if (signal.aborted) return [];
      return [
        {
          key: `${node.key}-${version}`,
          title: `${'版本'} ${version}`,
          isLeaf: true,
        },
      ];
    },
    [false, version],
  );
  return (
    <Space direction="vertical" align="stretch">
      <div>
        <Button variant="outline" onClick={() => setVersion((value) => value + 1)}>
          {'刷新数据'}
        </Button>
      </div>
      <Tree
        aria-label={'可刷新文档'}
        data={data}
        loadData={loadData}
        cacheKey={version}
        defaultExpandedKeys={['root']}
      />
      <Text tone="muted">
        {'根节点 key 保持不变，数据版本变化后会清除旧子节点并取消未完成的请求。'}
      </Text>
    </Space>
  );
}
```

展开代码

## Tree API

| 属性                    | 类型                                                                                                        | 默认值     | 说明                              |
| --------------------- | --------------------------------------------------------------------------------------------------------- | ------- | ------------------------------- |
| `data`                | `readonly `[`TreeNode`](#treenode)`[]`                                                                    | 必填      | 必需。树节点                          |
| `selectedKeys`        | `readonly string[]`                                                                                       | `[]`    | 受控 / 初始选中节点                     |
| `defaultSelectedKeys` | `readonly string[]`                                                                                       | `[]`    | 受控 / 初始选中节点                     |
| `onSelect`            | `(keys: string[], info: `[`TreeSelectInfo`](#treeselectinfo)`) => void`                                   | —       | 选择变化                            |
| `multiple`            | `boolean`                                                                                                 | `false` | 多选 / 允许选择                       |
| `selectable`          | `boolean`                                                                                                 | `true`  | 多选 / 允许选择                       |
| `checkable`           | `boolean`                                                                                                 | `false` | 展示复选框 / 禁止父子联动                  |
| `checkStrictly`       | `boolean`                                                                                                 | `false` | 展示复选框 / 禁止父子联动                  |
| `checkedKeys`         | `readonly string[]`                                                                                       | `[]`    | 受控 / 初始勾选节点                     |
| `defaultCheckedKeys`  | `readonly string[]`                                                                                       | `[]`    | 受控 / 初始勾选节点                     |
| `onCheck`             | `(keys: string[], info: `[`TreeCheckInfo`](#treecheckinfo)`) => void`                                     | —       | 勾选变化，包含联动结果                     |
| `expandedKeys`        | `readonly string[]`                                                                                       | `[]`    | 受控 / 初始展开节点                     |
| `defaultExpandedKeys` | `readonly string[]`                                                                                       | `[]`    | 受控 / 初始展开节点                     |
| `defaultExpandAll`    | `boolean`                                                                                                 | `false` | 初始展开全部节点                        |
| `onExpand`            | `(keys: string[], info: `[`TreeExpandInfo`](#treeexpandinfo)`) => void`                                   | —       | 展开变化                            |
| `disabled`            | `boolean`                                                                                                 | `false` | 禁用交互。                           |
| `showLine`            | `boolean`                                                                                                 | `false` | 禁用操作 / 显示图标 / 显示层级线             |
| `showIcon`            | `boolean`                                                                                                 | `false` | 禁用操作 / 显示图标 / 显示层级线             |
| `searchValue`         | `string`                                                                                                  | `''`    | 过滤节点，保留匹配项和祖先                   |
| `filterTreeNode`      | `(node: `[`TreeNode`](#treenode)`, query: string) => boolean`                                             | —       | 自定义搜索规则                         |
| `notFoundContent`     | `ReactNode`                                                                                               | 暂无选项    | 无匹配内容                           |
| `loadData`            | `(node: `[`TreeNode`](#treenode)`, signal: AbortSignal) => Promise<readonly `[`TreeNode`](#treenode)`[]>` | —       | 展开时加载子项，signal 用于取消             |
| `cacheKey`            | `string \| number`                                                                                        | —       | 数据版本改变时清空懒加载缓存，并取消旧请求。          |
| `nodeRender`          | `(node: `[`TreeNode`](#treenode)`) => ReactNode`                                                          | —       | 自定义节点展示内容。                      |
| `onLoad`              | `(node: `[`TreeNode`](#treenode)`, children: readonly `[`TreeNode`](#treenode)`[]) => void`               | —       | 子项加载成功                          |
| `onLoadError`         | `(error: unknown, node: `[`TreeNode`](#treenode)`) => void`                                               | —       | 加载失败，再次展开可重试                    |
| `virtual`             | `boolean`                                                                                                 | `false` | 仅渲染可视节点；大量节点时建议启用。              |
| `height`              | `number`                                                                                                  | `320`   | 高度，数字按像素计算                      |
| `itemHeight`          | `number`                                                                                                  | `42`    | 跟随区域控件高度和密度的预估行高，包含行间距；实际行会被测量。 |
| `draggable`           | `boolean \| ((node: `[`TreeNode`](#treenode)`) => boolean)`                                               | `false` | 允许通过手柄移动节点                      |
| `allowDrop`           | `(info: Omit<`[`TreeDropInfo`](#treedropinfo-api)`, 'data'>) => boolean`                                  | —       | 限制放置位置                          |
| `onDrop`              | `(info: `[`TreeDropInfo`](#treedropinfo-api)`) => void`                                                   | —       | 移动结果，调用方更新 data                 |

## TreeNode

| 属性                | 类型                                     | 默认值 | 说明                     |
| ----------------- | -------------------------------------- | --- | ---------------------- |
| `key`             | `string`                               | —   | 必需。唯一标识，必填             |
| `title`           | `ReactNode`                            | —   | 必需。节点内容，必填             |
| `children`        | `readonly `[`TreeNode`](#treenode)`[]` | —   | 子节点；使用同一结构             |
| `icon`            | `ReactNode`                            | —   | 自定义图标                  |
| `disabled`        | `boolean`                              | —   | 禁用交互。                  |
| `selectable`      | `boolean`                              | —   | 该节点能否选择 / 是否展示复选框，默认可用 |
| `checkable`       | `boolean`                              | —   | 该节点能否选择 / 是否展示复选框，默认可用 |
| `disableCheckbox` | `boolean`                              | —   | 禁止勾选并阻断父子勾选联动          |
| `searchLabel`     | `string`                               | —   | 标题为节点时提供用于搜索和读屏的文字     |
| `isLeaf`          | `boolean`                              | —   | false 允许异步加载子项         |

## TreeSelectInfo

| 属性              | 类型                                     | 默认值 | 说明         |
| --------------- | -------------------------------------- | --- | ---------- |
| `node`          | [`TreeNode`](#treenode)                | —   | 必需。本次操作节点  |
| `selected`      | `boolean`                              | —   | 必需。操作后是否选中 |
| `selectedNodes` | `readonly `[`TreeNode`](#treenode)`[]` | —   | 必需。全部选中节点  |

## TreeCheckInfo

| 属性                | 类型                                     | 默认值 | 说明         |
| ----------------- | -------------------------------------- | --- | ---------- |
| `node`            | [`TreeNode`](#treenode)                | —   | 必需。本次操作节点  |
| `checked`         | `boolean`                              | —   | 必需。操作后是否勾选 |
| `checkedNodes`    | `readonly `[`TreeNode`](#treenode)`[]` | —   | 必需。完整勾选节点  |
| `halfCheckedKeys` | `readonly string[]`                    | —   | 必需。半选节点标识  |

## TreeExpandInfo

| 属性         | 类型                      | 默认值 | 说明         |
| ---------- | ----------------------- | --- | ---------- |
| `node`     | [`TreeNode`](#treenode) | —   | 必需。本次操作节点  |
| `expanded` | `boolean`               | —   | 必需。操作后是否展开 |

## 类型

可导入以下类型定义属性、组装数据或处理回调。

```ts
import type { TreeProps, TreeNode, TreeSelectInfo, TreeCheckInfo, TreeExpandInfo } from '@ifhover/leaf-ui';
```

## 使用说明

每个节点的 `key` 应唯一且稳定。示例数据结构：

```ts lineNumbers file="../../src/examples/tree/data.ts"
import type { TreeNode } from '@ifhover/leaf-ui';
export const treeData: readonly TreeNode[] = [
  {
    key: 'design',
    title: '设计资源',
    children: [
      { key: 'components', title: '组件规范' },
      { key: 'colors', title: '品牌颜色' },
      { key: 'archive', title: '归档资源（不可选）', disabled: true },
    ],
  },
  {
    key: 'engineering',
    title: '研发项目',
    children: [
      { key: 'web', title: '网站' },
      { key: 'mobile', title: '移动应用' },
    ],
  },
];

```

勾选父节点会勾选可用子节点，禁用节点不会被联动修改。子节点部分选中时父节点呈半选；`checkStrictly` 让各节点独立勾选。多选时点击切换选择，Shift 可连续选择可见节点。

方向键上下移动，左右展开或收起，Home / End 跳至首尾，Enter 选择，空格勾选。输入文字可快速定位节点。搜索时会暂时展开匹配路径，清空后恢复原展开状态。

## 扩展类型

```ts
import type { TreeDropInfo } from '@ifhover/leaf-ui';
```

## TreeDropInfo API

| 属性         | 类型                                | 默认值 | 说明                 |
| ---------- | --------------------------------- | --- | ------------------ |
| `node`     | [`TreeNode`](#treenode)           | 必填  | 必需。操作节点            |
| `target`   | [`TreeNode`](#treenode)           | 必填  | 必需。接收拖放的目标节点。      |
| `position` | `'before' \| 'inside' \| 'after'` | 必填  | 必需。放在目标节点之前、内部或之后。 |
| `data`     | [`TreeNode`](#treenode)`[]`       | 必填  | 必需。层级数据            |

## moveTreeNode

需要自行调整树数据时可导入此纯函数。返回新树，不修改原数据；目标不存在、移动到自身或后代节点时返回 `null`。Tree 的 `onDrop` 已直接提供调整后的 data。

```ts
import { moveTreeNode } from '@ifhover/leaf-ui';

const next = moveTreeNode(data, 'design', 'team', 'inside');
```

| 参数         | 类型                                 | 说明               |
| ---------- | ---------------------------------- | ---------------- |
| `data`     | [`readonly TreeNode[]`](#treenode) | 原始树数据            |
| `key`      | `string`                           | 待移动的节点 key       |
| `target`   | `string`                           | 目标节点 key         |
| `position` | `'before' \| 'inside' \| 'after'`  | 相对目标的放置位置        |
| 返回值        | [`TreeNode[] \| null`](#treenode)  | 新的树；无法移动时返回 null |
