Vue3.4使用defineModel时,父组件绑定事件后子组件状态不更新
问题场景
Vue 3.4 中使用 defineModel() 实现父子组件状态双向绑定:父组件传递 enabled 状态给子组件,子组件通过复选框更新该状态,同时根据当前值是否偏离初始值切换徽章文本和样式。
但出现异常:未绑定 @update:enabled 事件的子组件工作正常,绑定该事件的子组件无法切换徽章状态。
父组件代码
<script setup lang="ts"> import CapabilityEntry from "../components/metrics/CapabilityEntry.vue"; import {useToast} from "vue-toastification"; const toast = useToast(); </script> <template> <h1>Capabilities</h1> <div class="space-y-5"> <CapabilityEntry id="binaryallowed" :enabled="false" friendly-name="Binary Allowed" description="Whether binary SMS content is allowed."/> <CapabilityEntry id="internationalallowed" :enabled="true" v-on:update:enabled="toast.success('Capability updated')"/> <CapabilityEntry id="outboundallowed" :enabled="true"/> <CapabilityEntry id="inboundallowed" :enabled="true" @update:enabled="toast.success('Capability updated')"/> </div> </template>
子组件代码
<script setup lang="ts"> import {unref} from "vue"; defineProps({ id: { type: String, required: true }, friendlyName: { type: String, required: false }, description: { type: String, required: false }, }); const enabled = defineModel("enabled"); const defaultValue = unref(enabled); </script> <template> <div class="relative flex items-start"> <div class="flex h-6 items-center"> <input :id="id" aria-describedby="checked" :name="id" type="checkbox" v-model="enabled" class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600 transition-all duration-200 ease-in-out" /> </div> <div class="ml-3 text-sm leading-6"> <label :for="id" class="font-medium text-gray-900"> {{ friendlyName ? friendlyName : id }} <span class="inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-600 transition-all duration-200 ease-in-out" v-if="enabled == defaultValue">Plan default</span> <span class="inline-flex items-center rounded-md bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 transition-all duration-200 ease-in-out" v-if="enabled != defaultValue">Deviates from plan</span> </label> <p id="comments-description" class="text-gray-500">{{description ? description : "No description"}}</p> </div> </div> </template>
问题原因
- 父组件传递字面量而非响应式变量:直接传递
:enabled="true"这类字面量,无法实现动态更新,违背了 Vue 单向数据流的设计逻辑。 - 自定义事件覆盖默认更新逻辑:手动绑定
@update:enabled事件时,仅执行了 toast 提示,未更新父组件状态,导致子组件的enabledprops 始终保持初始值。此时enabled.value与defaultValue始终相等,徽章无法切换。
未绑定事件的子组件,defineModel() 会在本地临时修改状态(不符合规范但表现出“正常”),所以徽章能切换,但这并非正确的双向绑定实现。
解决方案
正确实现双向绑定 + 状态变更提示
将父组件中的 enabled 改为响应式变量,使用 v-model:enabled 绑定,通过监听变量变化触发 toast,而非直接绑定 update:enabled 事件:
<script setup lang="ts"> import CapabilityEntry from "../components/metrics/CapabilityEntry.vue"; import {useToast} from "vue-toastification"; import {ref, watch} from "vue"; const toast = useToast(); // 定义响应式状态 const internationalAllowed = ref(true); const inboundAllowed = ref(true); // 监听状态变化,触发提示 watch(internationalAllowed, () => { toast.success('Capability updated'); }); watch(inboundAllowed, () => { toast.success('Capability updated'); }); </script> <template> <h1>Capabilities</h1> <div class="space-y-5"> <CapabilityEntry id="binaryallowed" :enabled="false" friendly-name="Binary Allowed" description="Whether binary SMS content is allowed."/> <CapabilityEntry id="internationalallowed" v-model:enabled="internationalAllowed"/> <CapabilityEntry id="outboundallowed" :enabled="true"/> <CapabilityEntry id="inboundallowed" v-model:enabled="inboundAllowed"/> </div> </template>
子组件无需修改
子组件的 defaultValue 初始化逻辑正确,保持现有代码即可,它会在组件挂载时获取父组件传递的初始值,用于后续判断是否偏离默认状态。
内容的提问来源于stack exchange,提问作者Limnic
相关产品推荐
相关产品推荐

