使用自定义组件时VeeValidate 4出现深层变更警告的问题
解决Vue + Vee-Validate动态字段行增删时的深层变更警告
问题场景
开发了两个自定义组件(custom-select下拉选择器、custom-text-field文本框)实现字段行动态增删功能,但增删行或修改组件值时,控制台出现以下Vue警告:
[Vue warn]: Detected a possible deep change on field
valueref, for nested changes please either set the entire ref value or usesetValueorhandleChange.
相关代码
自定义选择器组件(custom-select)
<template> <v-select v-model="value" :name="name" :error-messages="errorMessage" hide-details="auto"> <!-- @vue-skip --> <template v-for="(_, slotName) in $slots" #[slotName]="slotData"> <slot :name="slotName" v-bind="slotData" /> </template> </v-select> </template> <script setup lang="ts"> import { useField } from 'vee-validate' const { value, errorMessage } = useField(() => props.name) const props = defineProps({ name: { type: String, default: '' } }) </script>
自定义文本框组件(custom-text-field)
<template> <v-text-field v-model="value" :name="name" :error-messages="errorMessage" hide-details="auto"> <!-- @vue-skip --> <template v-for="(_, slotName) in $slots" #[slotName]="slotData"> <slot :name="slotName" v-bind="slotData" /> </template> </v-text-field> </template> <script setup lang="ts"> import { useField } from 'vee-validate' const { value, errorMessage } = useField(() => props.name) const props = defineProps({ name: { type: String, default: '' } }) </script>
字段行渲染代码
<form> <v-row v-for="(field, idx) in fields" :key="field.key" dense> <v-col :cols="field.value.boxShortName === 'custombox' ? 2 : 7"> <custom-select :name="`boxes[${idx}]`" label="Box Type" :items="props.boxOptions" density="compact" return-object /> </v-col> <!-- ... 其他字段 --> <v-col :cols="field.value.boxShortName === 'custombox' ? 2 : 3"> <custom-text-field :name="`boxes[${idx}].weight`" label="Wt" suffix="lbs" density="compact" /> </v-col> </form>
渲染逻辑脚本
const parcelBoxFormValidation = Yup.object({ boxes: Yup.array( Yup.object({ length: Yup .number() .transform((value, originalValue) => { return (originalValue === '' ? undefined : value) }) .typeError('Must be a number') .min(1, 'Min length is 1 inch') .required('Required'), breadth: Yup .number() .transform((value, originalValue) => { return (originalValue === '' ? undefined : value) }) .typeError('Must be a number') .min(1, 'Min breadth is 1 inch') .required('Required'), height: Yup .number() .transform((value, originalValue) => { return (originalValue === '' ? undefined : value) }) .typeError('Must be a number') .min(1, 'Min height is 1 inch') .required('Required'), weight: Yup .number() .transform((value, originalValue) => { return (originalValue === '' ? undefined : value) }) .typeError('Must be a number') .min(1, 'Min weight is 1 lb') .required('Required') }) ) }) const { meta, resetForm, values } = useForm({ validationSchema: parcelBoxFormValidation, initialValues: { boxes: [] } as any }) const { remove: removeBox, insert: insertBox, fields }: any = useFieldArray('boxes') onMounted(() => { boxRecommendation() }) const boxRecommendation = () => { resetForm({ values: { boxes: [...props.boxOptions] } }) } const addNewRow = async (idx: number) => { insertBox(idx + 1, props.boxOptions[props.boxOptions.length - 1]) } const removeRow = async (idx: number) => { removeBox(idx) }
Box Options示例
{ title: 'Test Box', boxShortName: 'testbox', length: '12', breadth: '10', height: '6', weight: '5' }, { title: 'Custom Box', boxShortName: 'custombox', length: '', breadth: '', height: '', weight: '' }
解决方案
警告核心原因是:Vee-Validate的useField返回的value是浅层响应式Ref,直接修改嵌套对象属性或动态数组操作触发深层变更时,Vue无法追踪到这些变更,从而抛出警告。
1. 自定义组件改用handleChange更新值
不再直接绑定v-model="value",手动绑定model-value和@update:model-value,通过Vee-Validate提供的handleChange方法更新值,确保变更被正确追踪:
修改后的custom-select组件
<template> <v-select :model-value="value" @update:model-value="handleChange" :name="name" :error-messages="errorMessage" hide-details="auto" > <!-- @vue-skip --> <template v-for="(_, slotName) in $slots" #[slotName]="slotData"> <slot :name="slotName" v-bind="slotData" /> </template> </v-select> </template> <script setup lang="ts"> import { useField } from 'vee-validate' const props = defineProps({ name: { type: String, default: '' } }) const { value, errorMessage, handleChange } = useField(() => props.name) </script>
修改后的custom-text-field组件
<template> <v-text-field :model-value="value" @update:model-value="handleChange" :name="name" :error-messages="errorMessage" hide-details="auto" > <!-- @vue-skip --> <template v-for="(_, slotName) in $slots" #[slotName]="slotData"> <slot :name="slotName" v-bind="slotData" /> </template> </v-text-field> </template> <script setup lang="ts"> import { useField } from 'vee-validate' const props = defineProps({ name: { type: String, default: '' } }) const { value, errorMessage, handleChange } = useField(() => props.name) </script>
2. 数组插入时使用深拷贝对象
插入新行时,不要直接引用props.boxOptions中的对象,创建深拷贝避免原对象被意外修改触发深层变更:
const addNewRow = async (idx: number) => { // 深拷贝对象,隔离与原props数据的引用 const newBox = JSON.parse(JSON.stringify(props.boxOptions[props.boxOptions.length - 1])) insertBox(idx + 1, newBox) }
3. 手动修改嵌套属性时的规范
如果需要通过代码修改嵌套属性,不要直接赋值,而是使用setValue方法或创建新对象替换数组项:
// 错误方式:直接修改嵌套属性 values.boxes[idx].weight = '10' // 正确方式1:使用setValue精准更新 setValue(`boxes[${idx}].weight`, '10') // 正确方式2:创建新对象替换数组项,保证响应式 const updatedBoxes = [...values.boxes] updatedBoxes[idx] = { ...updatedBoxes[idx], weight: '10' } setValue('boxes', updatedBoxes)
内容的提问来源于stack exchange,提问作者Ayan
相关产品推荐
相关产品推荐

