Quasar QTree 组件完全指南:从节点模型到虚拟滚动与无障碍树形交互
发布时间:2026/9/20 23:59:40 作者:尧图编辑部 阅读量:1,286

Quasar QTree 组件完全指南从节点模型到虚拟滚动与无障碍树形交互【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasarQTree 是 Quasar Framework 提供的树形组件通过一个 JSON 数组即可描述任意深度的层级数据目录、组织架构、权限树等并内置展开/折叠动画、勾选策略、懒加载、过滤、虚拟滚动与完整的 WAI-ARIA 无障碍支持。本文以官方文档为主体结合本仓库源码 ui/src/components/tree/QTree.js 与 API 定义 ui/src/components/tree/QTree.json 的佐证从节点模型定义、外观定制、性能调优到键盘导航与勾选策略系统讲解 QTree 的每一项能力读完即可在项目中直接落地使用。定义节点Defining the nodesQTree 的数据来源是nodesprop——一个由普通对象组成的数组每个对象代表一个节点通过嵌套的children属性表达层级关系。QTree 只按固定规则读取节点上的若干键其中key唯一标识、label文本、children子节点三个属性的名称都可以通过node-key、label-key、children-key三个 prop 自定义默认分别为label与childrennode-key必须显式指定。nodes: [ // array of Objects // node Object definition { // unique id, under the property named by node-key (required) id: fruits, // text of the node, under the property named by label-key label: Fruits, // (optional) icon, image or avatar shown before the label icon: restaurant_menu, iconColor: primary, // one of the Quasar Color Palette names // img: mountains.png, // from the /public folder // avatar: boy-avatar.png, // from the /public folder // (optional) the node cannot be selected, ticked, expanded or clicked disabled: false, // (optional) can the node be expanded? (default: true) expandable: true, // (optional) can the node be selected? (default: true) selectable: true, // (optional) called on click, receives the node handler: node {}, // (optional) with a tick strategy: show a checkbox, and can it be ticked? noTick: false, tickable: true, // (optional) tick strategy for this node only: leaf, leaf-filtered, strict, none tickStrategy: leaf, // (optional) scoped slot names for this nodes header and body, // without the header- / body- prefix header: story, // renders through the header-story slot body: story, // renders through the body-story slot // (optional) the sub-nodes, same shape, under the property named by children-key children: [ { id: apple, label: Apple }, { id: pear, label: Pear, disabled: true } ] }, { id: lazy, label: Loaded on first expand, // (optional) load the children on first expand through the lazy-load event; // do not set children on a lazy node lazy: true } ]节点对象字段一览下表总结了可在节点对象上声明的全部字段及其行为其中nodeKey指通过node-key指定的那个键Node PropertyTypeBehavior when not presentDescriptionnodeKeyString, NumberAn error is generatedNodes key. The key is picked from the key specified innodeKeyproperty.labelStringThe item has no labelNodes label. WhenlabelKeyprop is set the label is picked from that key.iconStringThe default icon is usedNodes icon.iconColorStringThe inherited color is usedNodes icon color. One from Quasar Color Palette.imgStringNo image is displayedNodes image. Use /public folder. Example: mountains.pngavatarStringNo avatar is displayedNodes avatar. Use /public folder. Example: boy-avatar.pngchildrenArrayThis node has no sub-nodesArray of nodes as children.disabledBooleanThe node is enabledIs node disabled?expandableBooleanThe node is expandableIs node expandable?selectableBooleanThe node is selectableIs node selectable?handlerFunctionNo extra function is calledCustom function that should be called on click on node. Receivesnodeas parameter.tickableBooleanThe node is tickable according to tick strategyWhen using a tick strategy, each node shows a checkbox. Should a nodes checkbox be disabled?noTickBooleanNode displays a checkboxWhen using a tick strategy, should node display a checkbox?tickStrategyStringTick strategy none is usedOverride global tick strategy for this node only. One of leaf, leaf-filtered, strict, none.lazyBooleanChildren are not lazy loadedShould children be lazy loaded? In this case also dont specify children prop.headerStringSlot default-header is usedNode header scoped slot name, without the required header- prefix. Example: story refers to header-story scoped slot.bodyStringSlot default-body is usedNode body scoped slot name, without the required body- prefix. Example: story refers to body-body scoped slot.从源码结构看节点模型会被一次性遍历并缓存为内部索引structurecomputed见 QTree.js每个节点记录其key、parentKey、childKeys、isParent、lazy、disabled、expandable、selectableBase、tickableBase及继承来的tickStrategy等事实后续的展开、勾选、过滤等状态操作全部基于这份索引而非反复扫描原始数组。基本用法Basic最简单的用法只需提供nodes与node-key两个 prop官方示例 docs/src/examples/QTree/Basic.vue 展示了带图标、头像、图片以及禁用节点的组合template q-tree :nodessimple node-keylabel / /template script setup const simple [ { label: Satisfied customers (with avatar), avatar: https://cdn.quasar.dev/img/boy-avatar.png, children: [ { label: Good food (with icon), icon: restaurant_menu, children: [ { label: Quality ingredients }, { label: Good recipe } ] }, { label: Good service (disabled node with icon), icon: room_service, disabled: true, children: [ { label: Prompt attention }, { label: Professional waiter } ] } ] } ] /script注意node-key是必填 prop节点的 key 必须唯一否则组件会报错。label-key与children-key默认取label与children如果你的数据使用name、roles等字段名可通过这两个 prop 映射见 QTree.json。无障碍支持v2.25从 Quasar v2.25 起QTree 遵循 WAI-ARIA 树模式treeview pattern实现了完整的无障碍语义组件根元素暴露roletree每个节点头部是roletreeitem父节点携带aria-expanded可选择的节点携带aria-selected启用勾选的节点携带aria-checked部分勾选的父节点会显示mixed状态禁用节点携带aria-disabled嵌套的子节点组rolegroup表达层级在virtual-scroll模式下行被扁平化渲染因此改用aria-level、aria-setsize、aria-posinset三个属性补偿层级信息。这一实现可以在 renderNodeHeader 中直接看到aria-expanded、aria-selected、aria-checked、aria-disabled与roletreeitem都是在此按节点元数据动态生成的。同时需要注意几点给树一个可访问名称通过aria-label或aria-labelledby在组件上设置。勾选框只是指针操作的视觉载体键盘勾选的路径是聚焦节点头部后按Space状态通过aria-checked播报。源码中勾选框被设置为tabindex-1且aria-hiddentrueQTree.js明确注释其为 a pointer affordance only。空状态文案来自语言包no nodes 与 no results 的提示使用 Quasar Language Pack 中的本地化字符串源码见 QTree.js 与 QTree.js也可用no-nodes-label、no-results-labelprop 覆盖。禁用节点仍然可导航WAI-ARIA 树模式要求每个可见节点都是 Tab 停靠点roving Tab stop因此即使叶子节点、禁用节点也参与 Tab 序列——禁用节点通过aria-disabled宣告自身但点击、展开、懒加载乃至其自身的handler都不会被触发。源码中onClick与onExpandClick都在入口处对localMeta.disabled true直接 returnQTree.jsfocusableKeys计算则把所有可见节点含禁用都纳入焦点序列QTree.js。键盘导航当某个树节点获得焦点时支持以下按键Arrow Up/Arrow Down在可见节点间移动焦点Arrow Right展开一个折叠的父节点或把焦点移到其第一个可见子节点Arrow Left折叠一个展开的父节点或把焦点移到其父节点Home/End跳到第一个 / 最后一个可见节点Enter执行节点的默认动作点击 / 选择Space切换展开状态在启用tick-strategy时可勾选的节点上则切换勾选框禁用节点上以上动作全部无效。键盘导航的逻辑集中在onNavigationKeyQTree.js它根据当前聚焦节点的元数据决定展开、折叠、移动焦点或滚动到可见节点。另外焦点移动不会触发整树重渲染源码用非响应式的focusedKey/tabStopKey直接操作 DOM 的tabindex属性moveTabStop渲染时再通过getTabKey()重新推导。外观定制无连接线No connector lines默认情况下 QTree 会绘制节点间的层级连接线。通过no-connectorsBoolean prop 可以去掉它们q-tree no-connectors :nodessimple node-keylabel /完整示例见 docs/src/examples/QTree/NoConnectors.vue。对应样式类为q-tree--no-connectors见 QTree.js 的 classes 计算。紧凑模式DensedenseBoolean prop 启用紧凑布局行高与内边距更小适合信息密度高的场景例如文件管理器侧栏示例见 docs/src/examples/QTree/DenseTree.vue。强制暗色模式Force dark modedarkBoolean prop 强制组件使用暗色主题不依赖全局的暗色模式设置示例见 docs/src/examples/QTree/Dark.vue。此外 QTree 还支持color整体主色、control-color勾选框等控件的颜色、text-color、selected-color等配色 prop详见 QTree.json。性能考量v2.25自 Quasar v2.25 起QTree 的渲染成本遵循只为屏幕上的内容付费原则折叠节点的子节点不会被渲染直到该节点第一次展开展开后子节点会保留在 DOM 中以display: none隐藏以便折叠/展开时仍能播放动画状态变更展开、勾选、选择、过滤、键盘导航只重渲染受影响的节点不会整体刷新。因此渲染成本与可见节点数成正比而不是与整棵树的大小成正比——绝大多数树不需要任何额外调优。源码中每个节点对应一个独立的QTreeNode实例QTree.js配合useKeyedFlags按 key 增量同步的响应式标记见 QTree.js和前值对象身份复用的快照计算getMetaRef见 QTree.js状态变化只会让真正发生翻转的节点失效并重渲染。需要注意如果你的代码曾查询从未展开节点的子 DOM现在必须先把这些节点展开否则它们根本不在 DOM 中。当同一时间可见的节点非常多时DOM 体量本身会成为瓶颈此时有两种手段按效果递增no-transitionBoolean prop关闭展开/折叠动画。这也允许 QTree 将折叠的子树直接从 DOM 中移除而不是为了动画保留它们在更早的 Quasar 版本中这是避免渲染折叠内容的唯一方式。推荐在数据量相对较大时使用q-tree no-transition ...virtual-scrollBoolean prop见下一节只在 DOM 中保留滚动视口附近的行。这是超大树的正确模式无论展开多少节点挂载、全部展开和过滤的开销都恒定不变。虚拟滚动Virtual scrollv2.25virtual-scrollBoolean prop 将可见节点渲染为扁平的虚拟化列表只有滚动视口附近的行外加可配置的缓冲存在于 DOM 中因此无论展开多少节点渲染成本都恒定。官方示例 docs/src/examples/QTree/VirtualScroll.vue 演示了一棵完全展开、共 4680 个节点的树——在这个模式下即使最大的树挂载、展开全部和过滤都只需毫秒级完成。该模式需要注意三点树自身成为滚动容器需要给它一个高度通过 CSS或者改用virtual-scroll-target指向一个滚动的祖先元素展开与折叠是即时的没有滑动过渡因此duration、no-transitionprop 与after-show/after-hide事件都不适用scrollTo方法可以把任意可见节点的行滚动到视口内键盘导航会自动完成这一动作。虚拟滚动相关的可调参数如下均定义于 QTree.jsonProp默认值说明virtual-scrollfalse启用虚拟滚动树自身成为滚动容器virtual-scroll-target—CSS 选择器或 DOM 元素指定滚动容器替代树自身virtual-scroll-item-size35dense 为 23一行像素高度用于初始渲染建议接近行最小高度virtual-scroll-slice-size10虚拟列表至少渲染的行数virtual-scroll-slice-ratio-before1可见区之前按行数比例渲染的缓冲virtual-scroll-slice-ratio-after1可见区之后按行数比例渲染的缓冲virtual-scroll-sticky-size-start/-end0滚动容器首尾固定区sticky的高度设置正确可提升滚动精度实现上虚拟滚动复用 QTree 的通用虚拟列表组合式函数useVirtualScrollQTree.js并在此基础上把树拍平成行virtualRowsQTree.js每行记录其深度、缩进引导线lines/cont用于绘制连接线、posinset/setsize等扁平化 ARIA 层级属性且该计算只依赖节点模型、展开状态和过滤结果勾选与选择不会触发重建。与 QSplitter、QTabPanels 集成QTree 经常作为内容导航与主内容区联动。官方示例 docs/src/examples/QTree/Splitter.vue 演示了QTree与QSplitter、QTabPanels的组合用法左侧树切换选中节点右侧面板随之切换。更多信息可参考 QSplitter 与 QTabPanels。自定义节点内容Customize contentQTree 提供四类作用域插槽用于完全掌控节点的呈现default-header所有节点的默认头部插槽default-body所有节点的默认主体插槽header-[name]命名头部插槽由节点上的header: name字段指定body-[name]命名主体插槽由节点上的body: name字段指定。每个插槽的作用域都包含treeQTree 实例、node节点对象、key、color、dark以及可响应式读写的expanded、ticked和只读的indeterminatev2.25。即插槽内可以直接对prop.expanded赋值来改变展开状态、对prop.ticked赋值来改变勾选状态作用域构建见 getSlotScope。示例 docs/src/examples/QTree/SlotsDefault.vue 展示了默认 header/body 插槽的用法docs/src/examples/QTree/SlotsCustomized.vue 展示了命名插槽header-story/body-story如何只作用于声明了对应字段的节点。[!WARNING] 点击或按下ENTER会选中树节点自定义头部此时会失焦按下SPACE会切换其展开状态。 如果不希望发生这种情况可以把自定义头部的内容包在div click.stop keydown.stop中或把监听器加到真正派发该事件的组件/元素上。手风琴模式、过滤与可选择手风琴模式Accordion设置accordionBoolean prop 后展开某个节点时其同级节点兄弟节点会被自动收起。示例见 docs/src/examples/QTree/Accordion.vue。实现上localSetExpanded在展开一个节点时会先折叠其所有可展开的兄弟QTree.js。过滤FilteringfilterString prop 指定过滤文本默认按label-key字段做不区分大小写的包含匹配。过滤时只要自身或任一后代命中该节点就会保留匹配结果向上冒泡见 filterMatches。示例见 docs/src/examples/QTree/FilterDefault.vue。可选择Selectable通过v-model:selected绑定当前选中节点的 key。示例见 docs/src/examples/QTree/Selectable.vue。no-selection-unsetprop 可禁止再次点击已选中节点时取消选择。懒加载Lazy loading当数据量很大或需要按需获取时可以给节点设置lazy: true注意懒加载节点上不要设置children。用户第一次展开该节点时QTree 会发出lazyLoad事件事件回调对象包含node待挂载子节点的节点对象key该节点的 keydone(children)加载成功后调用传入新的子节点数组fail()加载失败时调用。示例见 docs/src/examples/QTree/LazyLoad.vue。加载期间节点头部会显示 QSpinner 旋转指示器QTree.js。实现上setExpanded检测到lazy状态为未加载时会先把节点标记为loading发出lazyLoad事件待done回调写入子节点并进入loaded状态后再于下一次渲染时真正展开QTree.jsfail回调则会回滚懒加载状态并清理空的children。选择 vs 勾选 vs 展开QTree 将三种相互独立的状态分开管理选择Selection通过v-model:selected绑定指当前选中的单个节点。默认只改变其文字颜色可用selected-colorprop 调整被选中节点的头部还会带上q-tree__node--selectedCSS 类可以用自定义 CSS 进一步美化例如加背景色。勾选Ticking通过v-model:ticked绑定指每个节点关联的复选框状态key 数组。展开Expansion通过v-model:expanded绑定指哪些节点处于展开状态key 数组。以上三个属性都必须使用v-model:prop_name动态绑定才能正常工作例如q-tree :nodessimple node-keylabel v-model:expandedexpanded v-model:tickedticked v-model:selectedselected /示例见 docs/src/examples/QTree/Sync.vue。从源码看这三个状态分别对应selected、ticked、expanded三个update:*事件QTree.js并通过useKeyedFlags做按 key 的增量响应式同步QTree.js。勾选策略Tick strategyQTree 提供三种勾选策略外加默认的none禁用勾选StrategyDescriptionleafTicked nodes are only the leaves. Ticking a node influences the parents ticked state too (parent becomes partially ticked or ticked), as well as its children (all tickable children become ticked).leaf-filteredSame concept asleaf, only that this strategy applies only to filtered nodes (the nodes that remain visible after filtering).strictTicked nodes are independent of parent or children tick state.leaf只有叶子节点参与勾选。勾选某个节点会影响父节点状态父节点变为部分勾选或勾选也会影响其子节点所有可勾选的子节点都被勾选。leaf-filtered与leaf概念相同但只作用于过滤后仍可见的节点。strict每个节点的勾选状态完全独立与父/子节点无关。可以通过tick-strategyprop 设置全局策略也可以在节点模型的tickStrategy字段上为单个节点局部覆盖q-tree :nodessimple node-keylabel v-model:tickedticked :tick-strategytickStrategy default-expand-all /示例见 docs/src/examples/QTree/TickStrategy.vue该示例用q-option-group动态切换四种策略并实时展示ticked数组。源码层面策略在校验器中被限定为[none, strict, leaf, leaf-filtered]QTree.js每个节点会继承全局策略node.tickStrategy || parent 的策略 || props.tickStrategy见 QTree.js。部分勾选节点Partially ticked nodesv2.25在leaf与leaf-filtered策略下如果某父节点的可勾选子节点只有部分被勾选该父节点既不算勾选也不算未勾选因此不会出现在ticked模型中。此时可以用以下方式查询getIndeterminateNodes()返回所有部分勾选的节点对象顺序与nodes模型一致isIndeterminate(key)判断指定 key 的节点是否部分勾选getTickState(key)一次性获取三态值——true已勾选、null部分勾选、false未勾选。这个值正是该节点自身勾选框所持有的值可以直接绑定到你自己的QCheckbox的 model 上。对于strict策略节点勾选状态与子节点无关上述方法始终返回空/false。此外header 与 body 插槽的作用域中还有一个只读的indeterminate布尔值与ticked并列——它不可直接赋值因为节点只能通过勾选其子节点来变为部分勾选状态。template q-tree reftreeRef :nodessimple v-model:tickedticked node-keylabel tick-strategyleaf default-expand-all template #default-headerprop {{ prop.node.label }} q-badge v-ifprop.indeterminate classq-ml-sm colororange labelpartial / /template /q-tree /template完整示例见 docs/src/examples/QTree/Indeterminate.vue。实现层面父节点的ticked/indeterminate/indeterminateNextState由子节点的聚合结果自底向上推导getTickAggRefQTree.jsgetTickState则把两者合并为三态值QTree.js。自定义过滤方法Custom filter method默认过滤是标签包含文本不区分大小写。当需要更复杂的匹配规则时可以通过filter-methodprop 提供自定义函数其签名为(node, filter) Booleanq-tree :nodessimple node-keylabel :filterfilter :filter-methodcustomFilter /官方示例 docs/src/examples/QTree/FilterCustom.vue 演示了只有同时包含(*)才命中的过滤逻辑。源码中默认实现如下QTree.js(node, filter) { const filt filter.toLowerCase() return ( node[props.labelKey] node[props.labelKey].toLowerCase().includes(filt) ) }API 定义建议为了最佳性能应在作用域中引用该方法而不是内联定义QTree.json。公开方法与事件速查除了文档与示例覆盖的能力外QTree 还通过组件实例暴露了一系列编程式方法实现见 QTree.js完整签名见 QTree.json方法说明getNodeByKey(key)按 key 获取节点对象getParentNode(key)获取指定节点的父节点根节点返回undefinedgetTickedNodes()/getExpandedNodes()获取所有已勾选 / 已展开的节点对象数组getIndeterminateNodes()获取所有部分勾选的节点对象数组isTicked(key)/isExpanded(key)/isIndeterminate(key)按 key 查询勾选 / 展开 / 部分勾选状态getTickState(key)获取三态勾选值true/null/falsesetTicked(keys, state)/setExpanded(key, state)编程式设置勾选 / 展开状态expandAll()/collapseAll()展开 / 折叠全部节点scrollTo(key, edge?)仅虚拟滚动模式滚动到指定节点行edge支持start/center/end及-force变体事件方面除update:expanded、update:ticked、update:selected配合v-model使用、lazyLoad、afterShow、afterHide外虚拟滚动模式还会发出virtual-scroll事件含index、from、to、direction、ref等滚动位置信息。小结QTree 的核心价值在于用纯数据驱动 完善的状态分层把树形交互的复杂度收敛到一套可预测的 API 中。从本文可以看到节点模型通过node-key/label-key/children-key与业务数据解耦disabled、expandable、selectable、noTick、tickStrategy、lazy、header/body等字段覆盖了绝大多数交互定制需求三种正交状态选择 / 勾选 / 展开配合四种勾选策略与 v2.25 引入的部分勾选三态查询让权限树、目录选择等场景开箱即用按可见节点计费的增量渲染 no-transitionvirtual-scroll三级性能手段让数千节点的树也能保持流畅WAI-ARIA 树模式与完整键盘导航使其可被屏幕阅读器正确解读、可纯键盘操作。相关测试可参考 ui/src/components/tree/QTree.test.js 与 ui/src/components/tree/QTree.hydration.test.js官方示例集中在 docs/src/examples/QTree 目录下可作为直接复用的起点。【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考