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, and this page is available as Markdown at https://ifhover.github.io/leaf-ui/components/tree.md.

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
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
import { Tree } from '@ifhover/leaf-ui';

import { treeData } from './data';

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

搜索节点

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
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
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

属性类型默认值说明
datareadonly TreeNode[]必填必需。树节点
selectedKeysreadonly string[][]受控 / 初始选中节点
defaultSelectedKeysreadonly string[][]受控 / 初始选中节点
onSelect(keys: string[], info: TreeSelectInfo) => void—选择变化
multiplebooleanfalse多选 / 允许选择
selectablebooleantrue多选 / 允许选择
checkablebooleanfalse展示复选框 / 禁止父子联动
checkStrictlybooleanfalse展示复选框 / 禁止父子联动
checkedKeysreadonly string[][]受控 / 初始勾选节点
defaultCheckedKeysreadonly string[][]受控 / 初始勾选节点
onCheck(keys: string[], info: TreeCheckInfo) => void—勾选变化,包含联动结果
expandedKeysreadonly string[][]受控 / 初始展开节点
defaultExpandedKeysreadonly string[][]受控 / 初始展开节点
defaultExpandAllbooleanfalse初始展开全部节点
onExpand(keys: string[], info: TreeExpandInfo) => void—展开变化
disabledbooleanfalse禁用交互。
showLinebooleanfalse禁用操作 / 显示图标 / 显示层级线
showIconbooleanfalse禁用操作 / 显示图标 / 显示层级线
searchValuestring''过滤节点,保留匹配项和祖先
filterTreeNode(node: TreeNode, query: string) => boolean—自定义搜索规则
notFoundContentReactNode暂无选项无匹配内容
loadData(node: TreeNode, signal: AbortSignal) => Promise<readonly TreeNode[]>—展开时加载子项,signal 用于取消
cacheKeystring | number—数据版本改变时清空懒加载缓存,并取消旧请求。
nodeRender(node: TreeNode) => ReactNode—自定义节点展示内容。
onLoad(node: TreeNode, children: readonly TreeNode[]) => void—子项加载成功
onLoadError(error: unknown, node: TreeNode) => void—加载失败,再次展开可重试
virtualbooleanfalse仅渲染可视节点;大量节点时建议启用。
heightnumber320高度,数字按像素计算
itemHeightnumber42跟随区域控件高度和密度的预估行高,包含行间距;实际行会被测量。
draggableboolean | ((node: TreeNode) => boolean)false允许通过手柄移动节点
allowDrop(info: Omit<TreeDropInfo, 'data'>) => boolean—限制放置位置
onDrop(info: TreeDropInfo) => void—移动结果,调用方更新 data

TreeNode

属性类型默认值说明
keystring—必需。唯一标识,必填
titleReactNode—必需。节点内容,必填
childrenreadonly TreeNode[]—子节点;使用同一结构
iconReactNode—自定义图标
disabledboolean—禁用交互。
selectableboolean—该节点能否选择 / 是否展示复选框,默认可用
checkableboolean—该节点能否选择 / 是否展示复选框,默认可用
disableCheckboxboolean—禁止勾选并阻断父子勾选联动
searchLabelstring—标题为节点时提供用于搜索和读屏的文字
isLeafboolean—false 允许异步加载子项

TreeSelectInfo

属性类型默认值说明
nodeTreeNode—必需。本次操作节点
selectedboolean—必需。操作后是否选中
selectedNodesreadonly TreeNode[]—必需。全部选中节点

TreeCheckInfo

属性类型默认值说明
nodeTreeNode—必需。本次操作节点
checkedboolean—必需。操作后是否勾选
checkedNodesreadonly TreeNode[]—必需。完整勾选节点
halfCheckedKeysreadonly string[]—必需。半选节点标识

TreeExpandInfo

属性类型默认值说明
nodeTreeNode—必需。本次操作节点
expandedboolean—必需。操作后是否展开

类型

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

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

使用说明

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

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 选择,空格勾选。输入文字可快速定位节点。搜索时会暂时展开匹配路径,清空后恢复原展开状态。

扩展类型

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

TreeDropInfo API

属性类型默认值说明
nodeTreeNode必填必需。操作节点
targetTreeNode必填必需。接收拖放的目标节点。
position'before' | 'inside' | 'after'必填必需。放在目标节点之前、内部或之后。
dataTreeNode[]必填必需。层级数据

moveTreeNode

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

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

const next = moveTreeNode(data, 'design', 'team', 'inside');
参数类型说明
datareadonly TreeNode[]原始树数据
keystring待移动的节点 key
targetstring目标节点 key
position'before' | 'inside' | 'after'相对目标的放置位置
返回值TreeNode[] | null新的树;无法移动时返回 null