You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 10:30:46