Vue 2 迁移到 Vue 3 时的 v-model 语法不兼容 问题,警告 Extraneous non-props attributes (modelValue) [Vue warn]: Extraneous non-props attributes (modelValue) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.这个警告的原因是Vue 3 中v-model的语法变化与组件存在多根节点Fragment共同导致的。错误原因分析v-model默认属性名改变在 Vue 3 中组件上的v-model默认绑定的 prop 是modelValue触发的事件是update:modelValue。而你的u-dialog组件中定义的 prop 是value且触发的是input事件这是 Vue 2 的语法。产生非 prop 特性因为父组件使用了v-model实际上向子组件传递了modelValue。但子组件的props中并没有modelValue所以 Vue 把它当成了“非 prop 的特性 (non-props attribute)”。多根节点无法继承你的template中有page-meta和view两个根节点构成了 Fragment。Vue 无法将未声明的非 prop 特性自动挂载到多个根节点上因此抛出了这个警告。方案一 将组件改造为符合 Vue 3 标准的v-model推荐将组件的value改为modelValue并将触发的事件从input改为update:modelValue。1. 修改u-dialog组件的templatetemplate !-- 将 value 改为 modelValue -- page-meta :page-stylemodelValue ? overflow:hidden;height:100vh;position:fixed; : / view v-ifmodelValue !-- 下面的代码保持不变 -- view classdialog-model clickhandleMaskClose touchmove.stop.prevent(){} wheel.stop.prevent(){} !-- ... --修改u-dialog组件的scriptexport default { name: u-dailog, // 建议加上 inheritAttrs: false防止以后传入 class/style 时因多根节点再次报错 inheritAttrs: false, props: { // 将 value 改为 modelValue modelValue: { type: Boolean, default: false }, // ... 其他 props 保持不变 }, // 声明 emits (Vue 3 推荐做法) emits: [update:modelValue, cancel, confirm], data() { return {} }, // ... computed 保持不变 methods: { onCancel() { // 将 input 改为 update:modelValue this.$emit(update:modelValue, false) this.$emit(cancel) }, onConfirm() { // 将 input 改为 update:modelValue this.$emit(update:modelValue, false) this.$emit(confirm) }, handleMaskClose(){ this.maskClose this.onCancel() } } }修改后父组件的u-dialog v-modelshowDialog/u-dialog无需任何改动即可完美运行。方案二保留组件的value属性修改父组件的用法如果你不想修改u-dialog内部的 prop 名称可以利用 Vue 3 支持的v-model自定义参数特性。1. 修改父组件的调用方式给v-model加上参数:value明确告诉 Vue 绑定value属性。!-- 将 v-model 改为 v-model:value -- u-dialog v-model:valueshowDialog/u-dialog2. 修改u-dialog组件的script触发事件虽然属性名保持了value但 Vue 3 的事件触发机制变了需要将input改为update:value。methods: { onCancel() { // 将 input 改为 update:value this.$emit(update:value, false) this.$emit(cancel) }, onConfirm() { // 将 input 改为 update:value this.$emit(update:value, false) this.$emit(confirm) }, handleMaskClose(){ this.maskClose this.onCancel() } }你遇到的问题是典型的Vue 2 迁移到 Vue 3 时的v-model语法不兼容问题。建议直接采用方案一将组件彻底升级为 Vue 3 的标准写法同时加上inheritAttrs: false来规避多根节点Fragment带来的潜在特性继承警告。以 input 实现完整实例// components/u-input/u-input.vue template view classinput-wrapper !-- 用 key 强制重新渲染解决 uni-app input 值不同步的问题 -- input classuni-input :keyinputKey :placeholderplaceholder :valuemodelValue :typeinputType :passwordshowPassword inputhandleInput :maxlengthmaxlength v-bind$attrs / uni-icons v-ifshowClearIcon typeclose classuni-icon :sizeiconSize :coloriconColor clickhandleClearValue / uni-icons v-ifisPassWord classuni-icon :sizeiconSize :coloriconColor clickchangePassword :typeshowPassword ? eye-filled : eye-slash-filled / /view /template script export default { name: u-input, inheritAttrs: false, emits: [update:modelValue], props: { modelValue: { type: [String, Number], default: , }, type: { type: String, default: text, }, placeholder: { type: String, default: 请输入, }, iconSize: { type: Number, default: 20 }, //显式声明避免被 $attrs 透传出问题 maxlength: { type: [String, Number], default: -1, }, iconColor: { type: String, default: #808080 } }, data() { return { showPassword: false, showClearIcon: false, inputKey: 0, // 用于强制刷新 input }; }, computed: { isPassWord() { return this.type password || this.type safe-password; }, inputType() { return this.isPassWord ? text : this.type; } }, watch: { // 监听 value 变化保持清除按钮状态同步 modelValue: { immediate: true, handler(val) { this.showClearIcon val val.length 0; }, }, //type 变化时重置密码可见状态 isPassWord: { immediate: true, handler(val) { this.showPassword val; }, } }, methods: { handleInput(event) { const val event.detail.value; this.$emit(update:modelValue, val); this.showClearIcon val.length 0; }, handleClearValue() { this.$emit(update:modelValue, ); this.showClearIcon false; this.inputKey; }, changePassword() { this.showPassword !this.showPassword; }, }, }; /script style scoped langscss .input-wrapper { display: flex; flex-direction: row; align-items: center; width: 100%; } .uni-input { flex: 1; width: 100%; } .uni-icon { margin: 0 $uni-spacing-row-sm; } /style使用template u-input v-modeluser_phone classform-item-input typetel placeholder请输入手机号 maxlength11 /u-input /template script setup import { ref } from vue; import uInput from /components/u-input/u-input.vue const user_phone ref() /script

本周精选

本月热点