For AI agents: the complete documentation index is available at https://ifhover.github.io/leaf-ui/v/0.6.0/llms.txt, the full documentation bundle is available at https://ifhover.github.io/leaf-ui/v/0.6.0/llms-full.txt, and this page is available as Markdown at https://ifhover.github.io/leaf-ui/v/0.6.0/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