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

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>

问题根源

  1. 响应式关联断裂:watch回调中直接给formData重新赋值reactive(...),会切断模板、Vuelidate实例与原对象的响应式绑定,导致后续更新无法被感知。
  2. Vuelidate实例未同步:useVuelidate创建的验证实例绑定的是初始的validations和formData,手动修改validations的内容无法触发验证规则的更新,必须重新创建实例。
  3. 错误的实例修改方式:直接通过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>

关键修改说明

  1. 保持响应式对象引用:不再替换formData和validations,而是通过删除+新增属性的方式更新,确保响应式关联不中断。
  2. 重新创建验证实例:每次fields变化时,调用useVuelidate生成新的验证实例,绑定最新的规则和表单数据。
  3. 优化循环Key值:将v-for的key从idx改为field.name,避免表单元素复用导致的状态异常;错误循环使用error.$message作为key,避免重复渲染问题。
  4. 简化状态管理:移除多余的state对象,直接维护v$实例,减少不必要的嵌套。

内容的提问来源于stack exchange,提问作者Ivan Parra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 21:43:18