Vue 2.7中Vuelidate动态表单验证不更新问题求助
问题:Vue动态表单中Vuelidate验证规则无法随Props更新同步
我开发了一个可根据props传入的fields动态渲染表单的Vue组件,当props(随父组件布尔值变化而更新)改变时,Vuelidate的验证规则无法同步更新,已尝试数小时仍未解决。
相关代码如下:
<script setup> import { reactive, computed, watch, set } from "vue"; import { useVuelidate } from "@vuelidate/core"; const props = defineProps({ fields: { type: Array, required: true, }, reward: { type: Object, required: true, }, }); const emit = defineEmits(["submit"]); let formData = reactive( props.fields.reduce((acc, field) => { acc[field.name] = null; return acc; }, {}) ); let validations = reactive( props.fields.reduce((acc, field) => { acc[field.name] = { ...field.validations }; return acc; }, {}) ); let state = reactive({ v$: useVuelidate(validations, formData), }); watch( () => props.fields, async (newFields) => { // Reset the form Object.keys(formData).forEach((key) => { formData[key] = null; }); // Create a new reactive formData and validations formData = reactive( newFields.reduce((acc, field) => { acc[field.name] = null; return acc; }, {}) ); newFields.forEach((field) => { set(validations, field.name, { ...field.validations }); }); // Remove the validations of the fields that no longer exist and do not match the new fields Object.keys(validations).forEach((key) => { if (!newFields.find((field) => field.name === key)) { delete validations[key]; } }); // Update the vuelidate instance with the new validations Object.keys(validations).forEach((key) => { set(state.v$, key, validations[key]); }); // Reset the vuelidate instance state.v$.$reset(); // Mark all fields as touched state.v$.$touch(); console.log("state.v$", state.v$); }, { deep: true } ); const redeemEnabled = computed(() => { return ( props.reward.people_tour_stage_toins >= props.reward.toins || !props.reward.people_tour_stage?.has_redeemed ); }); function submitForm() { console.log("submitForm"); state.v$.$touch(); if (state.v$.$invalid || !redeemEnabled.value) { return; } emit("formSubmitted", formData); } defineExpose({ submitForm, }); </script> <template> <form @submit.prevent="submitForm"> <div class="tw-grid tw-grid-cols-1 lg:tw-grid-cols-2 tw-gap-2 lg:tw-gap-4"> <div v-for="(field, idx) in fields" :key="idx"> <label class="tw-font-roboto tw-text-xs lg:tw-text-sm tw-text-gray-400" :for="field.name" >{{ field.label }}</label > <input class="tw-form-input-small" :class="state.v$[field.name]?.$error ? 'tw-border-red-350' : ''" v-model="formData[field.name]" :name="field.name" :type="field.type" /> <div v-if="validations[field.name] && state.v$[field.name].$invalid"> <div v-for="(error, idx) in state.v$[field.name].$errors" :key="idx"> <span class="tw-text-red-350 tw-font-roboto tw-text-xs lg:tw-text-sm" v-if="idx === 0" >{{ error.$message }}</span > </div> </div> </div> </div> </form> </template> <style scoped></style>
问题根源
- 响应式关联断裂:watch回调中直接给
formData重新赋值reactive(...),会切断模板、Vuelidate实例与原对象的响应式绑定,导致后续更新无法被感知。 - Vuelidate实例未同步:
useVuelidate创建的验证实例绑定的是初始的validations和formData,手动修改validations的内容无法触发验证规则的更新,必须重新创建实例。 - 错误的实例修改方式:直接通过
set(state.v$, key, validations[key])修改验证实例属性,不符合Vuelidate的更新逻辑,无法同步规则。
修复后的代码
<script setup> import { reactive, computed, watch } from "vue"; import { useVuelidate } from "@vuelidate/core"; const props = defineProps({ fields: { type: Array, required: true, }, reward: { type: Object, required: true, }, }); const emit = defineEmits(["formSubmitted"]); // 初始化响应式对象,保持引用不变 let formData = reactive({}); let validations = reactive({}); // 初始化验证实例 let v$ = useVuelidate(validations, formData); // 统一更新表单数据和验证规则的方法 const updateFormAndValidations = (fields) => { // 清空原有数据和规则 Object.keys(formData).forEach(key => delete formData[key]); Object.keys(validations).forEach(key => delete validations[key]); // 填充新数据和规则 fields.forEach(field => { formData[field.name] = null; validations[field.name] = { ...field.validations }; }); // 重新创建验证实例,绑定最新规则和数据 v$ = useVuelidate(validations, formData); }; // 组件初始化时执行一次 updateFormAndValidations(props.fields); // 监听fields变化,更新表单和验证规则 watch( () => props.fields, (newFields) => { updateFormAndValidations(newFields); // 重置验证状态 v$.$reset(); }, { deep: true } ); const redeemEnabled = computed(() => { return ( props.reward.people_tour_stage_toins >= props.reward.toins || !props.reward.people_tour_stage?.has_redeemed ); }); function submitForm() { v$.$touch(); if (v$.$invalid || !redeemEnabled.value) { return; } emit("formSubmitted", { ...formData }); } defineExpose({ submitForm, }); </script> <template> <form @submit.prevent="submitForm"> <div class="tw-grid tw-grid-cols-1 lg:tw-grid-cols-2 tw-gap-2 lg:tw-gap-4"> <div v-for="(field, idx) in fields" :key="field.name"> <label class="tw-font-roboto tw-text-xs lg:tw-text-sm tw-text-gray-400" :for="field.name" >{{ field.label }}</label > <input class="tw-form-input-small" :class="v$[field.name]?.$error ? 'tw-border-red-350' : ''" v-model="formData[field.name]" :name="field.name" :type="field.type" /> <div v-if="validations[field.name] && v$[field.name].$invalid"> <div v-for="(error, idx) in v$[field.name].$errors" :key="error.$message"> <span class="tw-text-red-350 tw-font-roboto tw-text-xs lg:tw-text-sm" v-if="idx === 0" >{{ error.$message }}</span > </div> </div> </div> </div> </form> </template> <style scoped></style>
关键修改说明
- 保持响应式对象引用:不再替换
formData和validations,而是通过删除+新增属性的方式更新,确保响应式关联不中断。 - 重新创建验证实例:每次
fields变化时,调用useVuelidate生成新的验证实例,绑定最新的规则和表单数据。 - 优化循环Key值:将v-for的key从
idx改为field.name,避免表单元素复用导致的状态异常;错误循环使用error.$message作为key,避免重复渲染问题。 - 简化状态管理:移除多余的
state对象,直接维护v$实例,减少不必要的嵌套。
内容的提问来源于stack exchange,提问作者Ivan Parra
相关产品推荐
相关产品推荐

