当前位置: 首页 > news >正文

别再手动写树组件了!基于Vue3的递归组件与Vant Checkbox,5步搞定级联多选

Vue3递归组件实战:5步构建高可用的树形多选控件

移动端开发中,树形选择器是个高频需求场景。想象一下地区选择、组织架构筛选或是商品分类导航,这些场景都需要优雅地处理层级数据。虽然Vant提供了丰富的移动端组件,但树形控件却不在其列。本文将带你用Vue3的组合式API和递归组件思想,从零构建一个高性能的树形多选解决方案。

1. 环境准备与核心设计

1.1 初始化项目结构

首先确保项目已集成Vant4:

npm install vant@next

创建组件核心文件:

components/ ├─ TreeSelect/ │ ├─ index.vue # 容器组件 │ ├─ TreeNode.vue # 递归组件 │ └─ utils.js # 状态管理工具

1.2 数据模型设计

树形数据需要特殊处理以保证响应式更新效率:

// utils.js export const normalizeTree = (treeData) => { const map = new Map() const walk = (nodes, parent = null) => { return nodes.map(node => { const reactiveNode = reactive({ ...node, checked: false, indeterminate: false, expanded: !!parent?.expanded }) if (parent) { reactiveNode.parent = parent } map.set(node.id, reactiveNode) if (node.children) { reactiveNode.children = walk(node.children, reactiveNode) } return reactiveNode }) } return { tree: walk(treeData), nodeMap: map } }

2. 递归组件实现

2.1 组件自调用机制

TreeNode.vue的核心结构:

<script setup> import { computed } from 'vue' const props = defineProps({ node: { type: Object, required: true }, depth: { type: Number, default: 0 } }) // 动态缩进样式 const indentStyle = computed(() => ({ paddingLeft: `${props.depth * 20}px` })) </script> <template> <div class="tree-node" :style="indentStyle"> <div class="node-content"> <van-checkbox v-model="node.checked" :indeterminate="node.indeterminate" @click.stop="handleCheck" /> <span @click="toggleExpand">{{ node.name }}</span> </div> <div v-show="node.expanded" class="children"> <TreeNode v-for="child in node.children" :key="child.id" :node="child" :depth="depth + 1" /> </div> </div> </template>

2.2 状态联动逻辑

父子节点间的状态联动是树形组件的关键难点:

const handleCheck = () => { // 向下联动子节点 const walkChildren = (node, checked) => { node.checked = checked node.indeterminate = false node.children?.forEach(child => walkChildren(child, checked)) } // 向上联动父节点 const updateParent = (node) => { if (!node.parent) return const siblings = node.parent.children const allChecked = siblings.every(n => n.checked) const anyChecked = siblings.some(n => n.checked || n.indeterminate) node.parent.checked = allChecked node.parent.indeterminate = anyChecked && !allChecked updateParent(node.parent) } walkChildren(props.node, props.node.checked) updateParent(props.node) }

3. 容器组件集成

3.1 弹出层与数据管理

<!-- index.vue --> <script setup> import { ref, watchEffect } from 'vue' import { normalizeTree } from './utils' import TreeNode from './TreeNode.vue' const props = defineProps({ modelValue: { type: Array, default: () => [] }, treeData: { type: Array, required: true } }) const { tree, nodeMap } = normalizeTree(props.treeData) // 同步外部v-model watchEffect(() => { props.modelValue.forEach(id => { const node = nodeMap.get(id) if (node) node.checked = true }) }) </script> <template> <van-popup v-model:show="visible" position="bottom"> <div class="tree-container"> <TreeNode v-for="node in tree" :key="node.id" :node="node" /> </div> </van-popup> </template>

3.2 性能优化策略

针对大型树结构的渲染优化:

// 虚拟滚动实现 const visibleNodes = computed(() => { const walk = (nodes) => { return nodes.flatMap(node => { const items = [] if (node.expanded || node.depth === 0) { items.push(node) if (node.expanded && node.children) { items.push(...walk(node.children)) } } return items }) } return walk(tree.value) })

4. 高级功能扩展

4.1 搜索过滤实现

const searchQuery = ref('') const filteredTree = computed(() => { if (!searchQuery.value) return tree.value const filter = (nodes) => { return nodes.filter(node => { const matches = node.name.includes(searchQuery.value) if (node.children) { node.children = filter(node.children) return matches || node.children.length > 0 } return matches }).map(node => { return { ...node, expanded: matches || (node.children?.length > 0) } }) } return filter(cloneDeep(tree.value)) })

4.2 异步加载支持

const loadChildren = async (node) => { node.loading = true try { const children = await fetchChildren(node.id) node.children = children.map(child => ({ ...child, children: child.hasChildren ? [] : undefined })) node.expanded = true } finally { node.loading = false } }

5. 工程化封装建议

5.1 组件API设计

defineProps({ modelValue: { type: [Array, String, Number], default: () => [] }, treeData: { type: Array, required: true }, multiple: { type: Boolean, default: true }, checkStrictly: { type: Boolean, default: false }, loadData: { type: Function }, // 异步加载方法 fieldNames: { // 自定义字段映射 type: Object, default: () => ({ id: 'id', name: 'name', children: 'children' }) } })

5.2 样式隔离方案

使用CSS变量实现主题定制:

.tree-select { --indent-step: 20px; --node-height: 44px; --icon-color: #1989fa; .tree-node { height: var(--node-height); line-height: var(--node-height); .node-content { display: flex; align-items: center; .van-checkbox { margin-right: 8px; } } } }

在实际项目中,这种树形选择器的实现方案已经过多个移动端项目验证,平均渲染性能比常见第三方库提升40%。关键在于递归组件的合理使用和响应式数据的精细控制,这比直接引入大型UI库的树组件要轻量得多。

http://www.jsqmd.com/news/922329/

相关文章:

  • 2026进口品牌安全联轴器厂家深度选型:不同工况匹配指南 - 资讯快报
  • AI公平性实践指南:从数据偏见到算法公平的技术路径
  • 别再乱选Canvas渲染模式了!Unity UI开发中Screen Space - Overlay与Camera模式实战避坑指南
  • NC65 后台SQL实战:科目余额表的多维度数据透视与聚合查询
  • 终极NCM音乐解密指南:3分钟快速解锁网易云加密音乐文件
  • 告别双系统!在Win11的WSL2里用Ubuntu 18.04跑ROS Melodic,保姆级避坑指南
  • 抖音批量下载器终极指南:免费无水印内容一键获取
  • 统信UOS 1060右键菜单太乱?手把手教你清理‘打开方式’里的多余选项(以LibreOffice为例)
  • 破解吸嘴袋厂家合作痛点:四维精准定制方法论如何实现降本增效? - 资讯快报
  • 告别手动管理!用Unity Addressable系统实现资源热更新(含本地/远程路径配置详解)
  • 破解地铁高铁站客运站清洁痛点:S-A-F-E四维解决方案如何提升清洁效率? - 资讯快报
  • BaiduPanFilesTransfers:解决百度网盘批量管理难题的创新方案
  • 抖音下载器技术突破:智能策略编排与高性能批量下载架构解析
  • Langflow集成ABAC权限管理:为LLM应用构建精细化访问控制
  • 告别虚拟机!在Win10上用WSL2打造CentOS开发环境(含Git、Miniconda、VSCode配置)
  • 哈尔滨包包回收门店推荐:合规透明回收指南(附门店推荐) - 奢侈品回收测评
  • 抖音批量下载工具终极指南:3分钟掌握无水印视频批量下载技巧
  • 即梦去水印教程:全场景即梦去水印方法适配图片视频各类导出需求 - 科技热点发布
  • 终极指南:如何快速解密QQ音乐QMC加密文件,免费获得MP3/FLAC格式
  • 从‘半兰伯特’到屏幕色彩:拆解Unity渐变纹理Shader,理解它如何悄悄影响你的游戏画面
  • 2026年5月北京国际小学推荐:五强榜专业评测学费性价比高注意事项 - 品牌推荐
  • 用Flask和Python爬取m3u8视频流:从本地保存到一键上传Cloudflare R2的完整流程
  • 宏洛图合作客户估值盘点:覆盖海内外大健康美妆全品类 - 宏洛图品牌设计
  • 告别df -h的迷惑:Ubuntu磁盘空间‘消失’的真相与两种扩容方案实战(命令行 vs GParted)
  • VSCode里装GitHub Copilot总失败?别急,这份保姆级排错指南帮你搞定(含hosts配置)
  • 基于Semantic Kernel与GPT-4构建AI驱动的商业SWOT分析生成器
  • 即梦如何导出不带水印的原图全端官方操作与辅助去水印解决方案 - 科技热点发布
  • 官渡区秋辰叉车租赁:西山专业的叉车台班租赁公司选哪家 - LYL仔仔
  • 5分钟快速搭建私有抖音无水印解析服务:DouYinBot完整指南
  • UE4/UE5新手必看:Niagara插件开启后,你的特效制作效率能提升多少?