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

使用自定义组件时VeeValidate 4出现深层变更警告的问题

解决Vue + Vee-Validate动态字段行增删时的深层变更警告

问题场景

开发了两个自定义组件(custom-select下拉选择器、custom-text-field文本框)实现字段行动态增删功能,但增删行或修改组件值时,控制台出现以下Vue警告:

[Vue warn]: Detected a possible deep change on field value ref, for nested changes please either set the entire ref value or use setValue or handleChange.

相关代码

自定义选择器组件(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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 02:17:06