jk's notes
  • Tree 组件的使用

Tree 组件的使用

https://www.antdv.com/components/tree-cn#treenode

2024年7月19日

该组件有几个功能场景:

  • 递归展示 Tree 结构, 支持全部数据加载, 懒加载.
  • 结合文本框, 和响应式数据, 允许搜索 Tree 节点 (高亮, 或控制显示).
  • 允许拖拽调整 Tree 节点.
  • 支持右键菜单.

基本用法

组件:

  • Tree 组件. 树控件容器.
  • TreeNode 组件. 用于构造节点类型.

Tree 组件属性:

  • selectedKeys, 控制选中项, 受控属性 (用 v-model 绑定).
  • expandedKeys, 需要展开的节点, 受控属性 (用 v-model 绑定).
  • treeData, 绑定的数据源. 类型为: TreeNode[].
  • auto-expand-parent, 在指定展开某节点后, 是否自动展开其父节点. 与 expandedKeys 连用. 主要控制只选择一个节点时, 自动处理父节点的 key (不太喜欢使用该属性). 并且该属性与 expandedKeys 连用后, 会导致树无法折叠. 因此需要结合 expand 事件, 关闭 autoExpandParent, 并为 expandedKeys 赋值.

DataNode 类型:

export interface BasicDataNode {
    checkable?: boolean;
    disabled?: boolean;
    disableCheckbox?: boolean;
    icon?: IconType;
    isLeaf?: boolean;
    selectable?: boolean;
    switcherIcon?: IconType;
    /** 设置 TreeNode 样式. 不推荐使用 */
    class?: string;
    style?: CSSProperties;
    slots?: Record<string, string>;
    [key: string]: any;
}
export interface DataNode extends BasicDataNode {
    children?: DataNode[];
    key: string | number;
    title?: any;
}

示例:

<Tree :tree-data="treeDatas">
</Tree>
const treeDatas = ref<DataNode[]>([
  { title: '标题1', key: '值1', children: [
    { title: '子标题', key: '子值1', children: [] }
  ] },
  { title: '标题2', key: '值2', children: [] },
])

image-20240719112533801

如果要控制展开, 使用 expandedKeys 属性. 要获取选中的值, 可以使用 selectedKeys 属性, 或使用 select 事件.

如果需要多选, 使用 multiple 属性.

使用 title 插槽, 可以自定义每一个节点的显示.

要使得节点占据一整行, 使用 blockNode 属性.

image-20240719113000143

树右侧按钮, 不建议使用 title 插槽来实现, 当 tree 过深是布局会混乱. 建议使用右键菜单.

image-20240719114542310

异步加载数据

所谓异步加载数据, 即点击节点前的展开按钮, 触发 loadData 方法 (事件), 在节点中添加子节点.

涉及属性:

  • Tree 组件的 load-data 属性, 该属性是一个方法, 点击展开按钮时, 若节点不是叶子节点, 则触发该方法.
  • isLeaf 描述节点是否为叶子节点, 若为叶子节点, 则不显示前置展开按钮. 即不会再次加载数据.

load-data 方法签名:

(node: EventDataNode) => Promise<void>

EventDataNode 类型

export interface EventDataNode extends DataNode {
    expanded?: boolean;
    selected?: boolean;
    checked: boolean;
    loaded?: boolean;
    loading?: boolean;
    halfChecked?: boolean;
    dragOver?: boolean;
    dragOverGapTop?: boolean;
    dragOverGapBottom?: boolean;
    pos?: string;
    active?: boolean;
    dataRef?: DataNode;
    parent?: DataNode;
    eventKey?: Key;
}

重点注意 dataRef 属性.

Tree 初始化后, 传入的节点树对象会被包装成内部对象, 并提供一些附加属性, 从而构造出 EventDataNode 节点. 而其中的 dataRef 则表示其原始节点 (受控的 DataNode 数据).

需要注意的是, 必须提供 children 属性, 除非节点为叶子节点 (即 isLeaf 为 true), 否则在

使用步骤:

  1. 准备一个根节点.
  2. 定义一个 loadData 方法, 在方法中为 dataRef.children 添加子节点, 或必要的设置 isLeaf 为 true.
<Tree 
  v-model:selected-keys="selectedKeys"
  v-model:expanded-keys="expandedKeys"
  :tree-data="treeDatas"
  :load-data="loadDataHandler"
></Tree>
const selectedKeys = ref<string[]>([])
const expandedKeys = ref<string[]>([])
const treeDatas = ref<DataNode[]>([
  { title: '根节点', key: 'root', children: [] },
])

const loadDataHandler = async (node: EventDataNode) => {
  log('加载数据: %O', node)
  // log('受控节点与 dataRef 的比较: %s', treeDatas.value[0] == node.dataRef)
  node.dataRef.children.push({
    title: node.dataRef.title + '-p',
    key: node.dataRef.key + `-1`,
    children: [],
    // isLeaf: true,
  })
}

注释中表示, 在根节点上点击展开, 会提示 dataRef 的比较为 true.

image-20240719160823267

借助于 uuid 这个包, 生成不重复 key, 然后使用线性化的 cache 存储节点, 可以方便的生成需要的 Tree 结构.

关于自动展开的补充说明

原则上, 展开节点, 需要从被展开的节点开始, 向上找到根节点, 依次将 key 赋值给 expandedKeys 属性.

但是为了简单控制, 可以只将 展开节点的 key 赋值给 expandedKeys, 然后设置 autoExpandParent 为 true.

但此时会造成点击 被展开节点的 父节点时, 不再折叠树. 因此需要结合 expand 事件进行一些修整:

const expandHandler = (keys: string[]) => {
  log('展开事件: %o', keys)
  expandedKeys.value = keys // 保证基于受控属性自动填充 `expandedKeys`
  autoExpandParent.value = false // 不再一直展开, 将展开的控制交给受控属性
}

因此自动展开父节点, 一般只在初始化的时候使用.

简单总结: 自动展开节点值用在组件展开状态初始化的时候, 需要配合 expand 事件, 和受控的 expandedKeys 属性, 与 autoExpandParent 属性来来同步后续交互.

托拉拽功能说明

实现:

  1. 添加 draggable 属性, 来启用拖拽功能 (浏览器的功能).
  2. 注册 drop 事件. 该事件的逻辑是核心. 该事件参数中会记录, 拖拽的节点, 以及放置时的目标节点. 通过调整引用的节点对象的位置, 达到托拉拽功能 (改变节点顺序等信息后, 重现渲染页面).

其核心是拖拽最后放在哪里, drop 事件会提供属性用于描述最终放在哪里.

实现的逻辑是通过找到放在哪里, 从而调整绑定节点数据的构成, 来重新渲染 Tree 节点.

右键菜单的说明

右键菜单实际上利用 title 插槽, 在 title 上实现右键下拉菜单.

Last Updated:
Contributors: jk