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

如何为Inertia useForm()动态添加字段并实现响应式?

问题:Inertia.js动态生成表单无响应式

尝试实现一个可通过Props自动生成表单的全局组件,通过自定义useFormConstructor工具函数为Inertia.js的useForm()动态添加字段。当前能正常渲染自定义输入字段与标签,但表单字段无响应式表现:Vue DevTools显示useForm实例为响应式且包含formData对象,但输入时视图不更新,提交也无法正确获取数据。

相关代码

/composables/formConstructor.ts

import { onBeforeMount, reactive } from "vue";
import { useForm } from "@inertiajs/vue3";

interface Fields {
    type: string;
    name: string;
}
interface Inject {
    [key: string]: null;
}

export function useFormConstructor(fields: Fields[]) {
    const formData = reactive({});
    const form = useForm({ formData });

    onBeforeMount(() => {
        fields.forEach((field) => {
            if (!field.type) {
                console.error("Error: inputs and label arguments do not match");
                throw new Error(
                    `Form constructor expects input type but got undefined instead`
                );
            }
            if (!field.name) {
                throw new Error(
                    "Form constructor expects input name but got undefined instead"
                );
            }
            formData[field.name] = null;
        });
    });

    return form;
}

FormComponent.vue

<template>
  <div class="bg-white rounded-4 p-4 w-[70%]">
    <form
      class="mx-auto"
      @submit.prevent="form.post('https://myev-admin.test/login')"
    >
      <Label
        v-for="(field, index) in fields"
        :key="index"
        :for="field.label"
      />
      <Input
        v-for="(field, index) in fields"
        :id="field.id"
        :key="index"
        :type="field.type"
        :v-bind:value="form.formData[field.name]"
        :v-model="form.formData[field.name]"
        :placeholder="field.placeholder ? field.placeholder : ''"
        :name="field.name"
        :required="field.required"
      />
      <Button
        class="inline-block mx-auto"
        @confim="$emit('confirm')"
      >
        {{ $t(`${buttonText}`) }}<Button />
      </Button>
    </form>
  </div>
</template>

<script setup>
import Button from "./Button.vue";
import Label from "./Label.vue";
import Input from "./Input.vue";
import { useFormConstructor } from "../Composables/formConstructor";

defineEmits("confirm");
const props = defineProps({
  buttonText: {
    type: String,
    default: "confirm",
    required: true,
  },
  fields: {
    type: Array,
    required: true,
    default: () => [],
  },
  method: {
    type: String,
    default: "get",
    required: true,
  },
});

const form = useFormConstructor(props.fields);
</script>

使用表单的组件

<FormComponent
  :fields="[
    {
      type: 'text',
      placeholder: 'cynthiacopschelp@gmail.com',
      vModel: 'form.email',
      name: 'email',
      required: true,
      id: 'email',
      label: 'email',
    },
    {
      type: 'password',
      placeholder: 'verysafepaswword',
      vModel: 'form.password',
      name: 'password',
      required: true,
      id: 'password',
      label: 'password',
    },
  ]"
  :button-text="'login'"
  :method="'post'"
/>

问题根源

  1. Inertia useForm初始化时机错误:useForm需要在调用时就拿到完整的字段结构,当前实现先传入空formData创建实例,再动态添加字段,导致新增字段无法被Inertia的响应式系统监听。
  2. 模板v-model写法错误::v-model是错误语法(v-model不需要绑定符),且错误绑定到嵌套的form.formData,而Inertia的useForm返回的实例本身就是顶层响应式对象,字段直接挂在实例上。
  3. 动态属性响应式追踪缺失:Vue的reactive对象动态添加属性时,若未提前声明,无法自动触发响应式更新。

解决方案

1. 重构useFormConstructor,提前构建完整字段

先根据fields数组生成完整的初始表单数据,再传入useForm:

import { useForm } from "@inertiajs/vue3";

interface Field {
    type: string;
    name: string;
}

export function useFormConstructor(fields: Field[]) {
    // 提前构建完整的初始表单数据
    const initialFormData = fields.reduce((acc, field) => {
        if (!field.type) {
            console.error("Error: input type is required");
            throw new Error(`Form field missing type: ${JSON.stringify(field)}`);
        }
        if (!field.name) {
            console.error("Error: input name is required");
            throw new Error(`Form field missing name: ${JSON.stringify(field)}`);
        }
        acc[field.name] = null;
        return acc;
    }, {} as Record<string, null>);

    // 用完整初始数据创建Inertia form实例
    const form = useForm(initialFormData);

    return form;
}

2. 修正FormComponent.vue模板与逻辑

  • 去掉v-model的绑定符,直接绑定到form[field.name]
  • 修复按钮嵌套错误,调整label的绑定逻辑
<template>
  <div class="bg-white rounded-4 p-4 w-[70%]">
    <form
      class="mx-auto"
      @submit.prevent="form.post('https://myev-admin.test/login')"
    >
      <div v-for="(field, index) in fields" :key="index">
        <Label :for="field.id" :label="field.label" />
        <Input
          :id="field.id"
          :type="field.type"
          v-model="form[field.name]"
          :placeholder="field.placeholder || ''"
          :name="field.name"
          :required="field.required"
        />
      </div>
      <Button
        class="inline-block mx-auto"
        @click="$emit('confirm')"
      >
        {{ $t(buttonText) }}
      </Button>
    </form>
  </div>
</template>

<script setup>
import Button from "./Button.vue";
import Label from "./Label.vue";
import Input from "./Input.vue";
import { useFormConstructor } from "../Composables/formConstructor";

defineEmits("confirm");
const props = defineProps({
  buttonText: {
    type: String,
    default: "confirm",
    required: true,
  },
  fields: {
    type: Array,
    required: true,
    default: () => [],
  },
  method: {
    type: String,
    default: "get",
    required: true,
  },
});

const form = useFormConstructor(props.fields);
</script>

3. 简化使用组件的字段配置

移除无用的vModel字段:

<FormComponent
  :fields="[
    {
      type: 'text',
      placeholder: 'cynthiacopschelp@gmail.com',
      name: 'email',
      required: true,
      id: 'email',
      label: 'email',
    },
    {
      type: 'password',
      placeholder: 'verysafepaswword',
      name: 'password',
      required: true,
      id: 'password',
      label: 'password',
    },
  ]"
  :button-text="'login'"
  :method="'post'"
/>

关键说明

  • Inertia的useForm返回的是顶层响应式对象,所有表单字段直接挂在该对象上,无需嵌套formData。
  • 必须在调用useForm时传入完整的初始字段结构,确保Inertia能正确监听每个字段的变化。
  • Vue的v-model无需绑定符,直接使用v-model="form[field.name]"即可实现双向绑定。

内容的提问来源于stack exchange,提问作者Tristan Coopman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 03:57:06