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

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>

问题原因

  1. 父组件传递字面量而非响应式变量:直接传递 :enabled="true" 这类字面量,无法实现动态更新,违背了 Vue 单向数据流的设计逻辑。
  2. 自定义事件覆盖默认更新逻辑:手动绑定 @update:enabled 事件时,仅执行了 toast 提示,未更新父组件状态,导致子组件的 enabled props 始终保持初始值。此时 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 09:54:56