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

Vue3+Vuetify3组件警告:update:modelValue未声明于emits选项

Vue3+Vuetify3侧边对话框组件emit警告问题:已声明update:modelValue仍报错

我正在基于Vue3+Vuetify3开发项目,编写了一个侧边对话框组件,父组件通过v-model控制其显示状态,但控制台持续输出警告:

component emitted event "update:modelValue" but it is neither declared in the emits option nor as an "onUpdate:modelValue" prop.

我已经在组件的emits接口中声明了update:modelValue事件,却仍出现该警告,想知道问题出在哪里。

侧边对话框组件代码

<template>
    <div>
        <slot name="activator" :show="() => setShowDialog(true)" :close="() => setShowDialog(false)" />
        <VDialog
            v-model="componentData.showDialog"
            @click:outside="onClickOutside"
            :width="componentProperties.width"
            :min-width="componentProperties.minWidth"
            :max-width="componentProperties.maxWidth"
            :content-class="contentClass"
            persistent
            transition="slide-x-reverse-transition"
        >
            <VCard v-show="componentData.showDialog" v-fill-parent>
                <VCardTitle v-if="hasTitle" :class="componentProperties.titleClass">
                    <VRow justify="center" align="center">
                        <slot name="title">
                            <span v-if="componentProperties.title">{{ componentProperties.title }}</span>
                        </slot>
                        <VSpacer />
                        <VCol v-if="componentProperties.showTopCloseButton" cols="1">
                            <VBtn
                                @click.stop.prevent="() => setShowDialog(false)"
                                color="transparent"
                                elevation="0"
                                size="medium"
                            >
                                <IconifyMdiClose />
                            </VBtn>
                        </VCol>
                    </VRow>
                </VCardTitle>

                <slot name="body">
                    <VCardText>
                        <slot name="content-prepend" />
                        <slot>
                            <slot name="content" />
                        </slot>
                        <slot name="content-append" />
                    </VCardText>
                </slot>
                <VCardActions>
                    <slot name="actions" :show="() => setShowDialog(true)" :close="() => setShowDialog(false)" />
                </VCardActions>
            </VCard>
        </VDialog>
    </div>
</template>

<script setup lang="ts">
import { Events } from '@enums/events';
export interface IDialogConfirmComponentProperties {
    modelValue?: boolean;
    modelData?: any;
    title?: string;
    isActive?: boolean;
    showTopCloseButton?: boolean;
    titleClass?: string;
    fillHeight?: boolean;
    persistent?: boolean;
    minWidth?: string | number;
    maxWidth?: string | number;
    width?: string | number;
}

export interface IDialogConfirmComponentEvents {
    (e: 'onOutsideClicked'): void;
    (e: 'update:isActive', val?: boolean): void;
    (e: 'update:modelValue', visible?: boolean): void;
}

export interface IDialogConfirmComponentData {
    showDialog?: boolean;
}

const componentProperties = withDefaults(defineProps<IDialogConfirmComponentProperties>(), {
    persistent: false,
    fillHeight: true,
    modelValue: false,
    isActive: true,
    showTopCloseButton: false,
    titleClass: 'pt-5 text-wrap',
    width: 'auto',
    minWidth: 650,
    maxWidth: 650,
});
const componentData = reactive<IDialogConfirmComponentData>({
    showDialog: false,
});
const emits = defineEmits<IDialogConfirmComponentEvents>();
const slots = useSlots();
const hasTitle = computed(() => !!slots['title'] || !!componentProperties?.title);
const contentClass = computed(() => {
    return ['side-dialog', componentProperties.fillHeight ? 'fill-height' : ''].join(' ');
});

function onClickOutside(payload: MouseEvent) {
    emits('onOutsideClicked');
    if (componentProperties.persistent) return;
    setShowDialog(false);
}

watch(
    () => componentData.showDialog,
    showDialog => {
        componentData.showDialog = showDialog;
        emitsDialogVisibleChanges();
    },
);
watch(
    () => componentProperties.modelValue,
    showDialog => {
        componentData.showDialog = showDialog;
    },
);
function setShowDialog(value: boolean) {
    componentData.showDialog = value;
}
function emitsDialogVisibleChanges() {
    if (componentProperties.modelValue === undefined || componentProperties.modelValue === null) return;
    emits('update:isActive', componentData.showDialog);
    emits('update:modelValue', componentData.showDialog);
}
</script>
<style lang="css">
.side-dialog {
    inset-block-start: 0 !important;
    inset-inline-end: 0 !important;
}
</style>

父组件调用代码

<template>
    <SideDialog title="Configurations" show-top-close-button v-model="isDialogVisible">
        <template #activator="{ show }">
            <VBtn :variant="componentProperties.variant" @click.stop.prevent="show">
                <VIcon>mdi-cog</VIcon>
            </VBtn>
        </template>
        <VRow justify="center" align="center">
            <span>teste</span>
        </VRow>
    </SideDialog>
</template>
<script setup lang="ts">
import type { ButtonVariant } from '@/types/vuetify';
export interface IConfigurationComponentProperties {
    variant?: ButtonVariant;
}
const componentProperties = withDefaults(defineProps<IConfigurationComponentProperties>(), {
    variant: 'plain',
});
const isDialogVisible = ref(false);
</script>

问题原因分析

  1. VDialog自带事件冒泡:你给VDialog绑定了v-model="componentData.showDialog",而Vuetify的VDialog组件本身会在状态变化时(比如点击遮罩关闭对话框)触发update:modelValue事件。由于你没有监听并处理这个事件,它会向上冒泡,被Vue误认为是你的SideDialog组件发出的事件,但这个由VDialog触发的事件并未包含在你声明的emits列表中,因此触发警告。

  2. watch逻辑的无限循环风险:你在监听componentData.showDialog的回调中,又执行了componentData.showDialog = showDialog,这会导致watch被反复触发,形成无限循环,同时也会触发不必要的事件emit。


解决方案

步骤1:处理VDialog的update:modelValue事件

将VDialog的v-model拆分为手动绑定modelValue和监听update:model-value,阻止事件冒泡并同步到组件内部状态:

<VDialog
    :model-value="componentData.showDialog"
    @update:model-value="handleDialogUpdate"
    @click:outside="onClickOutside"
    :width="componentProperties.width"
    :min-width="componentProperties.minWidth"
    :max-width="componentProperties.maxWidth"
    :content-class="contentClass"
    persistent
    transition="slide-x-reverse-transition"
>

添加对应的处理函数:

function handleDialogUpdate(newValue: boolean) {
    componentData.showDialog = newValue;
}

步骤2:修复watch的无限循环问题

移除watch回调中对componentData.showDialog的重复赋值:

watch(
    () => componentData.showDialog,
    showDialog => {
        emitsDialogVisibleChanges();
    },
);

步骤3:优化事件emit逻辑(可选)

由于watch会监听内部状态变化并自动emit事件,可简化setShowDialog函数:

function setShowDialog(value: boolean) {
    componentData.showDialog = value;
}

内容的提问来源于stack exchange,提问作者Vinicius Andrade

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 23:09:54