Naive UI 树型选择组件 TreeSelect 完整实战指南:Props、勾选策略、异步加载与源码解析
2026/9/21 3:31:58 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】naive-ui

A Vue 3 Component Library. Fairly Complete. Theme Customizable. Uses TypeScript. Fast.

项目地址:https://gitcode.com/gh_mirrors/na/naive-ui
点击查看免费下载

导读

本文以 Naive UI 官方文档中的 TreeSelect 演示与 API 文档 为骨架,结合组件源码与全部 16 个官方 demo(位于 src/tree-select/demos/zhCN),系统讲解n-tree-select的配置项、勾选策略、异步加载、自定义字段等核心能力。读完本文,你将掌握 TreeSelect 从基础单选到级联多选、从过滤搜索到异步加载的完整实战方案,并理解其底层基于treemateselect组合的实现原理。

官方文档开头有句玩笑话:"据说 99% 的人分不清它和 Cascader 的区别。" 树型选择(TreeSelect)与级联选择(Cascader)都会展示层级数据,但 TreeSelect 的弹层是一棵可展开的树,且天然支持多选、勾选、过滤、异步加载等能力,适合需要在表单中选择树形数据节点的场景。

一、组件定位与快速上手

n-tree-select是 Naive UI 中用于在树形数据中进行单选或多选的组件。它本质上是Tree 与 Select 的组合体:选中后呈现为一个输入框(Select 形态),展开后是一棵可交互的树(Tree 形态)。这一点在源码 src/tree-select/src/TreeSelect.tsx 中体现得非常直接——组件内部同时使用了NInternalSelection(内部选择框)与NTree(树组件),并通过treeOption2SelectOption这类工具函数把树节点转换为选择器选项。

最小可用示例

官方基础 demo(basic.demo.vue)展示了一个最简单的单选用法:

<script lang="ts" setup> import type { TreeSelectOption } from 'naive-ui' function handleUpdateValue( value: string | number | Array<string | number> | null, option: TreeSelectOption | null | Array<TreeSelectOption | null> ) { console.log(value, option) } const options = [ { label: 'Rubber Soul', key: 'Rubber Soul', children: [ { label: 'Drive My Car', key: 'Drive My Car', disabled: true }, { label: 'Norwegian Wood', key: 'Norwegian Wood' } // ...更多子节点 ] }, { label: 'Let It Be', key: 'Let It Be Album', children: [ { label: 'Two Of Us', key: 'Two Of Us' } // ...更多子节点 ] } ] </script> <template> <n-tree-select :options="options" default-value="Drive My Car" @update:value="handleUpdateValue" /> </template>

从这个示例可以提炼出三个核心约定:

  1. 数据模型:每个选项是TreeSelectOption,至少需要label(显示文本)与key(唯一标识)两个字段,children字段表示子节点;
  2. 受控/非受控:用default-value设置默认选中,用value+@update:value实现受控;
  3. value 的类型:单选时是string | number | null,多选时是数组。查看源码 src/tree-select/src/interface.ts 中Value类型的定义:
export type Value = string | number | Array<string | number> | null

二、TreeSelect Props 完整参数表(含默认值与版本说明)

官方 API 文档以表格形式给出了全部 Props,这里完整继承并补充说明。版本号列以当前仓库文档为准,标注了该属性从哪个版本开始可用。

名称类型默认值说明版本
allow-checking-not-loadedbooleanfalse是否允许级联勾选还没有完全加载的节点。使用该属性时请记住value可能是不完整的,并注意勾选行为与后端计算逻辑的一致性,尤其是有禁用节点时2.28.1
cascadebooleanfalse使用 checkbox 进行多选时是否级联
checkablebooleanfalse是否使用 checkbox 进行选择
check-strategystring'all'勾选策略:all显示全部选中节点;parent只显示父节点(父节点下所有子节点都选中时);child只显示子节点
children-fieldstring'children'替代TreeSelectOption中的 children 字段名
clearablebooleanfalse是否可清除
clear-filter-after-selectbooleantrue可过滤且多选时,选中一个选项后是否保留当前搜索关键词2.25.3
consistent-menu-widthbooleantrue是否使菜单宽度与输入框一致,打开会禁用虚拟滚动
default-valuestring \| number \| Array<string \| number> \| nullnull默认选中的 key
default-expand-allbooleanfalse默认展开全部
default-expanded-keysArray<string \| number>[]默认展开节点的 key
disabledbooleanfalse是否禁用
ellipsis-tag-popover-propsPopoverPropsundefined选中选项过多省略显示时,预览弹出popover的属性2.37.0
expanded-keysArray<string \| number>undefined展开节点的 key(受控)
indentnumber24树每一级缩进的大小2.41.1
indeterminate-keysstring \| numberundefined部分选中选项的 key
filterablebooleanfalse是否可过滤
filter(pattern: string, option: TreeSelectOption) => boolean-过滤器函数
get-children(option: any) => unknownundefined获取当前选项的子选项2.38.1
key-fieldstring'key'替代TreeSelectOption中的 key 字段名
label-fieldstring'label'替代TreeSelectOption中的 label 字段名
disabled-fieldstring'disabled'替代TreeSelectOption中的 disabled 字段名2.32.2
loadingbooleanfalse是否加载中2.28.3
max-tag-countnumber \| 'responsive'undefined多选时最多直接显示多少选项,设为'responsive'保证最多一行
menu-propsHTMLAttributesundefined菜单的 DOM 属性2.22.0
multiplebooleanfalse是否支持多选
node-props(info: { option: TreeSelectOption }) => HTMLAttributesundefined节点的 HTML 属性2.30.7
optionsTreeSelectOption[][]选项
override-default-node-click-behavior(info: { option: TreeSelectOption }) => 'toggleExpand' \| 'toggleSelect' \| 'toggleCheck' \| 'default' \| 'none'undefined覆盖默认的节点点击行为2.37.0
placeholderstring'请选择'占位信息
placement'top-start' \| 'top' \| 'top-end' \| 'right-start' \| 'right' \| 'right-end' \| 'bottom-start' \| 'bottom' \| 'bottom-end' \| 'left-start' \| 'left' \| 'left-end''bottom-start'选择器的弹出位置2.25.0
render-label(info: { option: TreeSelectOption, checked: boolean, selected: boolean }) => VNodeChildundefined节点内容的渲染函数2.30.7
render-prefix(info: { option: TreeSelectOption, checked: boolean, selected: boolean }) => VNodeChildundefined节点前缀的渲染函数2.30.7
render-suffix(info: { option: TreeSelectOption, checked: boolean, selected: boolean }) => VNodeChildundefined节点后缀的渲染函数2.30.7
render-switcher-icon() => VNodeChildundefined节点展开开关的渲染函数2.30.7
render-tag(props: { option: TreeSelectOption, handleClose: () => void }) => VNodeChildundefined控制标签的渲染2.30.7
separatorstring' / '数据分隔符
show-linebooleanfalse是否显示树的连接线2.44.0
show-pathbooleanfalse是否在选择器中显示选项路径
size'small' \| 'medium' \| 'large''medium'组件尺寸
status'success' \| 'warning' \| 'error'undefined验证状态2.27.0
tostring \| HTMLElement \| falsebody菜单的容器节点,false会待在原地
valuestring \| number \| Array<string \| number> \| nullundefined选中的 key
virtual-scrollbooleantrue是否开启虚拟滚动
watch-propsArray<'defaultCheckedKeys' \| 'defaultSelectedKeys' \| 'defaultExpandedKeys'>undefined需要检测变更的默认属性,检测后组件状态会更新。注意:watch-props本身不是响应式的2.36.0
on-blur(e: FocusEvent) => voidundefinedBlur 时的回调
on-focus(e: FocusEvent) => voidundefinedFocus 时的回调
on-load(node: TreeSelectOption) => Promise<void>undefined异步加载数据的回调函数2.27.0
on-update:expanded-keys(value: Array<string \| number>, meta: { node: TreeOption \| null, action: 'expand' \| 'collapse' \| 'filter' }) => voidundefined展开节点更新的回调meta2.34.0
on-update:indeterminate-keys(keys: Array<string \| number>) => voidundefined节点部分勾选项变化时的回调函数
on-update:value(value: string \| number \| Array<string \| number> \| null, option: TreeSelectOption \| null \| Array<TreeSelectOption \| null>, meta: { node: TreeOption \| null, action: 'select' \| 'unselect' \| 'delete' \| 'clear' }) => voidundefined更新值的回调meta2.34.0

源码中的 props 定义印证

以上绝大多数属性的默认值都能在 src/tree-select/src/TreeSelect.tsx 的treeSelectProps中找到直接对应,例如:

export const treeSelectProps = { bordered: { type: Boolean, default: true }, cascade: Boolean, checkable: Boolean, clearable: Boolean, clearFilterAfterSelect: { type: Boolean, default: true }, consistentMenuWidth: { type: Boolean, default: true }, defaultValue: { type: [String, Number, Array] as PropType< string | number | Array<string | number> | null >, default: null }, disabled: { type: Boolean as PropType<boolean | undefined>, default: undefined }, filterable: Boolean, checkStrategy: { type: String as PropType<CheckStrategy>, default: 'all' } // ...后续更多属性 }

注意checkStrategy的类型CheckStrategy来自treemate库,说明勾选策略的计算逻辑由treemate这一树形数据工具库完成——这是 TreeSelect 内部树数据处理的核心依赖之一。

事件回调的 meta 参数

on-update:valueon-update:expanded-keys自 2.34.0 起新增了meta参数。以on-update:value为例,其完整类型定义在 src/tree-select/src/interface.ts 中,action有四种取值:

  • 'select'/'unselect':节点被选中/取消选中;
  • 'delete':通过标签上的删除按钮移除某个已选项;
  • 'clear':通过清除按钮整体清空。

实战中可以利用meta.action区分交互来源,例如统计"用户手动取消"与"整体清空"的操作日志。

三、多选、级联与勾选策略(最核心的进阶能力)

1. 多选与 Checkbox 勾选

TreeSelect 支持两种多选形态:

  • multiple:点击节点即可多选,选中项以标签(tag)形式展示,见 multiple.demo.vue:
<template> <n-tree-select multiple :options="options" :default-value="['Norwegian Wood']" @update:value="handleUpdateValue" /> </template>
  • multiple+checkable:每个节点前出现 checkbox,见 checkbox.demo.vue。官方 demo 特别强调:要得到 checkbox 效果,checkablecascademultiple需要同时设定
<template> <n-tree-select multiple cascade checkable :options="options" :default-value="['Norwegian Wood']" /> </template>

其中cascade决定勾选父节点时是否自动级联勾选其全部子节点。

2. check-strategy:三种勾选策略

check-strategy.demo.vue 演示了check-strategy的三种取值如何影响"最终显示的勾选节点集合":

  • all:显示全部选中节点(默认值);
  • parent:当父节点下所有子节点都选中时,只显示父节点(合并展示);
  • child:只显示子节点(父节点全选时隐藏父节点,仅展示叶子层级)。

demo 中用n-radio-group动态切换策略,非常直观:

<template> <n-radio-group v-model:value="checkStrategy"> <n-radio-button value="all">All</n-radio-button> <n-radio-button value="parent">Parent</n-radio-button> <n-radio-button value="child">Child</n-radio-button> </n-radio-group> <n-tree-select multiple cascade checkable :check-strategy="checkStrategy" :options="options" :default-value="['Dig It', 'go']" /> </template>

使用建议:当树层级很深、后端只需要"父级权限"这类粒度时,parent能显著简化提交的数据;当需要精确到叶子节点时,child更合适。注意策略只影响"显示/提交的 key 集合",不会改变树内勾选状态本身。

3. 半选(indeterminate)状态

级联勾选中,当一个父节点的部分子节点被选中时,父节点会进入"半选"状态。与之相关的 API:

  • indeterminate-keys:受控地设置部分选中节点的 key;
  • on-update:indeterminate-keys:半选状态变化时的回调;
  • 实例方法getIndeterminateData():获取半选节点的 keys 与 options(见后文 Methods 章节)。

四、自定义字段:对接后端数据模型

实际项目中后端返回的树形数据字段名几乎不可能恰好是label/key/children。TreeSelect 提供了三个字段映射属性:

属性默认值作用
label-field'label'替代显示文本字段名
key-field'key'替代唯一标识字段名
children-field'children'替代子节点数组字段名
disabled-field(2.32.2)'disabled'替代禁用字段名

custom-field.demo.vue 演示了后端数据使用whateverLabel/whateverKey/whateverChildren时如何适配:

<template> <n-tree-select :options="options" default-value="Drive My Car" label-field="whateverLabel" key-field="whateverKey" children-field="whateverChildren" /> </template>

源码视角:字段映射不仅在数据层生效,还贯穿到树节点 → 选择器选项的转换过程。查看 src/tree-select/src/utils.ts,treeOption2SelectOption通过rawNode[labelField]动态读取字段:

export function treeOption2SelectOption( tmNode: TreeSelectTmNode, labelField: string ): SelectBaseOption { const { rawNode } = tmNode return { ...rawNode, label: rawNode[labelField] as string, value: tmNode.key } }

结合show-pathtreeOption2SelectOptionWithPath还支持用separator(默认' / ')拼接路径作为显示标签:

label: path.map(v => v.rawNode[labelField]).join(separator)

这就是show-path在选择框中显示"父级 / 子级"路径的实现基础。

五、过滤搜索:filterable 与 filter

1. 基础过滤

filterable开启后,输入框可输入关键词对树进行过滤。filterable.demo.vue 演示了两种组合:

<n-tree-select filterable :options="options" default-value="Drive My Car" clearable /> <n-tree-select multiple checkable filterable :clear-filter-after-select="false" :options="options" :default-value="['Norwegian Wood']" clearable />

第二个示例中用到了clear-filter-after-select:默认值为true,即选中后清空搜索关键词;设为false则在多选连续操作时保留关键词,方便连续勾选多个匹配项。

2. 自定义过滤函数

默认过滤是按 label 文本匹配,如需按 key、拼音、自定义字段等匹配,可通过filter属性传入自定义函数:

filter: (pattern: string, option: TreeSelectOption) => boolean

例如按 key 匹配:

<script lang="ts" setup> function filterByKey(pattern: string, option: TreeSelectOption) { return String(option.key).includes(pattern) } </script> <template> <n-tree-select filterable :filter="filterByKey" :options="options" /> </template>

六、异步加载与大数据量优化

1. on-load 异步加载子节点

on-load回调(2.27.0+)用于按需加载子节点。async.demo.vue 给出了完整实现,其中关键约定是:异步加载时,所有isLeaffalsechildren不为数组的节点会被视为未加载的节点,展开时触发on-load

<script lang="ts" setup> function getChildren(option: TreeSelectOption) { const children = [] for (let i = 0; i <= (option as { depth: number }).depth; ++i) { children.push({ label: `${option.label}-${i}`, key: `${option.label}-${i}`, depth: (option as { depth: number }).depth + 1, isLeaf: option.depth === 3 }) } return children } function handleLoad(option: TreeSelectOption) { return new Promise<void>((resolve) => { window.setTimeout(() => { option.children = getChildren(option) resolve() }, 1000) }) } </script> <template> <n-tree-select v-model:value="value" multiple checkable :options="options" :cascade="cascade" :check-strategy="checkStrategy" :show-path="showPath" :allow-checking-not-loaded="cascade" :on-load="handleLoad" /> </template>

要点总结:

  1. 初始options中未加载的节点必须显式设置isLeaf: false(TreeSelectOption 属性表 中注明isLeaf在异步展开场景下是必须的,2.27.0);
  2. on-load必须返回Promise<void>,在请求完成后通过给option.children赋值并resolve()通知组件刷新;
  3. 配合allow-checking-not-loaded(2.28.1):当级联勾选需要跨过尚未加载的节点时开启;官方文档特别提醒——开启后value可能不完整,且勾选行为要与后端计算逻辑保持一致,尤其要注意禁用节点的情况;
  4. get-children(2.38.1)可自定义"获取子选项"的方式,用于适配特殊数据结构。

2. 虚拟滚动与菜单宽度

  • virtual-scroll(默认true):树节点数量大时(数百上千级节点)开启虚拟滚动保证流畅渲染;
  • consistent-menu-width(默认true):菜单宽度与输入框一致;打开时会禁用虚拟滚动,两者不可兼得,需根据节点量权衡;
  • max-tag-count:多选时最多直接显示多少标签,超出部分折叠,设为'responsive'会保证最多占一行;可配合ellipsis-tag-popover-props(2.37.0)配置溢出标签的预览 Popover 样式。

七、界面定制:插槽、渲染函数与节点交互

1. 插槽(Slots)

官方文档定义了四个插槽,均无参数:

名称参数说明版本
header()菜单头部区域的 slot2.40.0
action()菜单操作区域的 slot2.22.0
arrow()选择箭头 slot2.30.4
empty()菜单无数据时的 slot2.22.0

action.demo.vue 演示了headeraction的用法——比如在菜单底部放"清空""确认"等自定义操作:

<template> <n-tree-select :options="options" default-value="Drive My Car" @update:value="handleUpdateValue" > <template #header> 不知道放些什么 </template> <template #action> 你可以在这里自定义一些操作 </template> </n-tree-select> </template>

2. 渲染函数(2.30.7+)

需要精细控制树节点内容时,可使用四个渲染函数,它们都能拿到{ option, checked, selected }上下文:

属性签名作用
render-label({ option, checked, selected }) => VNodeChild节点内容渲染
render-prefix({ option, checked, selected }) => VNodeChild节点前缀渲染
render-suffix({ option, checked, selected }) => VNodeChild节点后缀渲染(如图标、数量角标)
render-switcher-icon() => VNodeChild展开/折叠箭头渲染
render-tag({ option, handleClose }) => VNodeChild多选标签渲染(handleClose可关闭该标签)

3. 节点点击行为与节点属性

  • override-default-node-click-behavior(2.37.0):覆盖默认点击行为,返回值为'toggleExpand' | 'toggleSelect' | 'toggleCheck' | 'default' | 'none',可针对不同节点定制"点击=展开"或"点击=勾选"等策略;
  • node-props(2.30.7):为节点 DOM 注入HTMLAttributes,例如按选项动态设置titleclassdata-*属性;
  • indent(2.41.1):调整树每级缩进(默认24px)。

4. 树形展示细节

  • show-line(2.44.0):显示树节点间的连接线,见 show-line.demo.vue:
<template> <n-tree-select show-line default-expand-all :options="options" default-value="Drive My Car" /> </template>
  • show-path:选择框中以路径形式展示选中项(如Rubber Soul / Drive My Car),分隔符由separator控制(默认' / ');
  • default-expand-all/default-expanded-keys/expanded-keys:分别控制默认全展开、默认展开指定节点、受控展开状态;watch-props(2.36.0)可让默认属性(如defaultExpandedKeys)在外部变化后同步更新组件内部状态,但注意watch-props本身不是响应式的,应在初始化时一次性传入。

八、表单集成:验证状态与实例方法

1. 验证状态

status属性(2.27.0)可脱离表单独立设置'success' | 'warning' | 'error'三种验证状态,见 status.demo.vue:

<template> <n-space vertical> <n-tree-select status="warning" placeholder="" /> <n-tree-select status="error" placeholder="" /> </n-space> </template>

当 TreeSelect 被包裹在n-form-item中时,验证状态会自动从表单校验结果同步(内部通过useFormItemmixin 实现),此时通常无需手动设置status

2. 实例方法(Methods)

通过模板 ref 可调用以下实例方法:

名称类型说明版本
blur() => void失焦2.34.0
blurInput() => void输入失焦2.35.0
focus() => void聚焦2.34.0
focusInput() => void输入聚焦2.35.0
getCheckedData() => { keys: Array<string \| number>, options: Array<TreeOption \| null> }获取选中的数据2.34.0
getIndeterminateData() => { keys: Array<string \| number>, options: Array<TreeOption \| null> }获取半选的数据2.34.0

这些方法的类型定义与 src/tree-select/src/interface.ts 中TreeSelectInst接口完全一致。典型用法(如在"确认提交"时统一读取勾选与半选数据):

<script lang="ts" setup> import { ref } from 'vue' import type { TreeSelectInst } from 'naive-ui' const treeSelectRef = ref<TreeSelectInst | null>(null) function handleSubmit() { const checked = treeSelectRef.value?.getCheckedData() const indeterminate = treeSelectRef.value?.getIndeterminateData() console.log('checked keys:', checked?.keys) console.log('indeterminate keys:', indeterminate?.keys) } </script> <template> <n-tree-select ref="treeSelectRef" multiple cascade checkable :options="options" /> <n-button @click="handleSubmit">提交</n-button> </template>

3. TreeSelectOption 属性

TreeSelectOption的完整定义如下(注意isLeaf仅在异步加载场景下必须):

名称类型说明版本
keystring \| number选项的 key,需要唯一,可使用key-field修改字段名
labelstring选项的显示内容,可使用label-field修改字段名
children?TreeSelectOption[]节点的子选项
disabled?boolean是否禁用选项
isLeaf?boolean节点是否是叶节点,在异步展开状态下是必须的2.27.0

在源码 src/tree-select/src/interface.ts 中,TreeSelectOption基于 Tree 的TreeOptionBase派生,并保留了索引签名[k: string]: unknown——这意味着你可以在选项上挂载任意自定义字段(如depthicon),配合渲染函数或node-props使用。

九、常见问题与最佳实践小结

  1. TreeSelect 与 Cascader 怎么选:需要"展开式选择树节点 + 多选/勾选/过滤/异步加载"用 TreeSelect;需要"逐级联动选择完整路径"(如省市区)用 Cascader;
  2. 勾选数据不完整:使用allow-checking-not-loaded或混合check-strategy时,务必理解提交的 value 语义,必要时用getCheckedData()/getIndeterminateData()读取完整节点信息;
  3. 大数据量卡顿:保持virtual-scroll开启;如需菜单与输入框等宽(consistent-menu-width)会强制关闭虚拟滚动,节点量大时需权衡;
  4. 异步加载的坑:未加载节点必须显式isLeaf: falseon-load中给option.children赋值后要resolve()
  5. 字段映射:对接后端异构数据时优先用label-field/key-field/children-field/disabled-field,避免在业务层做昂贵的树转换;
  6. 表单联动:在n-form-item中使用时自动继承校验状态,脱离表单时用status手动控制。

十、进一步探索

  • 完整 demo 源码:全部 16 个中文演示位于 src/tree-select/demos/zhCN(basicmultiplecheckboxcheck-strategycustom-fieldfilterableasyncactionshow-linestatusfile-picker等),英文版位于 src/tree-select/demos/enUS,可直接对照学习;
  • 组件核心实现:src/tree-select/src/TreeSelect.tsx(props 定义与整体逻辑)、src/tree-select/src/interface.ts(类型与实例接口)、src/tree-select/src/utils.ts(树节点转选择器选项)、src/tree-select/src/styles/index.cssr.ts(样式);
  • 组件入口与导出:src/tree-select/index.ts;
  • 底层依赖:树数据管理由treemate完成,树组件本身见 src/tree/src/Tree.tsx 与 src/tree/src/interface.ts。
  • 前端
  • UI组件

【免费下载链接】naive-ui

A Vue 3 Component Library. Fairly Complete. Theme Customizable. Uses TypeScript. Fast.

项目地址:https://gitcode.com/gh_mirrors/na/naive-ui
点击查看免费下载
上一篇:geoip在Web应用中的实践:用户地理位置检测和个性化内容推荐终极指南
下一篇:如何快速掌握Nullboard:极简看板工具的完整入门指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询