Vue递归复选框组件如何向父组件同步选中值?
递归Tree组件选中状态同步解决方案
核心逻辑
Vue的props是只读的,不能直接用v-model绑定修改。递归组件里只要通过事件向上透传,就能把底层节点的选中状态同步到顶层父组件——不管嵌套多少层,事件会自动冒泡,中间层不需要额外处理逻辑。
具体实现步骤
1. 子组件(TreeItem)改造
放弃直接用v-model绑定selectedItems,改成手动控制复选框状态,同时通过emit触发父组件更新:
<template> <div class="tree-item"> <label> <input type="checkbox" :checked="isNodeChecked" @change="handleCheckChange" > {{ node.label }} </label> <!-- 递归调用时透传emit事件 --> <div v-if="node.children?.length" class="tree-children"> <TreeItem v-for="child in node.children" :key="child.id" :node="child" :selected-items="selectedItems" @update:selected-items="$emit('update:selectedItems', $event)" /> </div> </div> </template> <script setup> import { computed } from 'vue' // 定义props和emit const props = defineProps({ node: { type: Object, required: true }, selectedItems: { type: Array, default: () => [] } }) const emit = defineEmits(['update:selectedItems']) // 计算当前节点是否被选中 const isNodeChecked = computed(() => props.selectedItems.includes(props.node.id)) // 处理复选框变更 const handleCheckChange = (e) => { const isChecked = e.target.checked // 基于原数组创建新数组,保证数据不可变性 let updatedSelected = [...props.selectedItems] if (isChecked) { // 选中节点:添加自身ID,可选联动子节点 if (!updatedSelected.includes(props.node.id)) { updatedSelected.push(props.node.id) } // 可选逻辑:自动选中所有子节点 props.node.children?.forEach(child => { if (!updatedSelected.includes(child.id)) { updatedSelected.push(child.id) } }) } else { // 取消选中:移除自身ID,可选联动子节点 updatedSelected = updatedSelected.filter(id => id !== props.node.id) // 可选逻辑:自动取消所有子节点 props.node.children?.forEach(child => { updatedSelected = updatedSelected.filter(id => id !== child.id) }) } // 向上触发更新事件 emit('update:selectedItems', updatedSelected) } </script>
2. 父组件调用
直接用Vue的v-model语法糖绑定即可(因为我们用了符合规范的update:selectedItems事件):
<template> <TreeItem :node="rootTreeData" v-model:selectedItems="userForm.userModules" /> </template> <script setup> import { reactive } from 'vue' import TreeItem from './TreeItem.vue' // 父组件的数据源 const userForm = reactive({ userModules: [] // 初始选中的节点ID集合 }) // 假设的树形结构数据 const rootTreeData = { id: 1, label: '根节点', children: [/* 子节点数据 */] } </script>
关键细节
- 递归组件的事件透传:只需要在递归调用时把
@update:selected-items直接转发给上层的emit,中间层不需要做任何额外处理,事件会一直传到顶层父组件 - 数据不可变性:必须基于原
selectedItems创建新数组,不能直接修改原数组,否则会触发Vue的只读警告 - 联动逻辑:如果需要父节点和子节点的联动(比如子节点全选时自动选中父节点),可以在父组件里通过计算属性判断父节点的选中状态,再通过emit更新选中数组
内容的提问来源于stack exchange,提问作者Glauco Cetara
相关产品推荐
相关产品推荐

