如何实现 Vue 动态表单生成器(原理 + 实战)

如何实现 Vue 动态表单生成器(原理 + 实战)

作者:杜松混合茉莉的风

日期:2025.4.16

引言

在前端开发中,表单是非常常见的交互元素。然而,当表单的结构和字段需要根据不同的业务场景动态变化时,手动编写每个表单的代码会变得非常繁琐。Vue 作为一个流行的前端框架,提供了强大的组件化和动态渲染能力,结合 Vuelidate 库进行表单校验,我们可以轻松实现一个动态表单生成器。本文将详细介绍实现动态表单生成器的原理,并通过实战演示如何使用 Vue 构建一个灵活的动态表单。

原理分析

动态组件渲染

Vue 提供了component标签,通过 :is 属性可以动态指定要渲染的组件。结合 v-for 指令,我们可以遍历 JSON 配置对象,根据配置中的组件类型动态渲染不同的表单项。例如:

vue 复制代码
<template>
  <div v-for="item in formConfig" :key="item.name">
    <label :for="item.name">{{ item.name }}</label>
    <component :is="getComponent(item.type)"
      v-model="formData[item.name]"
      v-bind="item.props" 
    />
  </div>
</template>

在上述代码中,getComponent 方法根据 item.type 返回对应的组件,从而实现动态渲染。

表单校验

为了确保用户输入的数据符合业务规则,我们需要对表单进行校验。Vuelidate 是一个轻量级的 Vue 表单验证库,它提供了丰富的验证规则和便捷的使用方式。我们可以根据 JSON 配置中的 rule 字段动态生成校验规则,并使用 useVuelidate 函数对表单数据进行校验。例如:

vue 复制代码
const getRules = () => {
  const rules = {};
  formConfig.value.forEach(i => {
    if (i.rule) {
      rules[i.name] = {};
      if (i.rule.required === 'required') {
        rules[i.name].required = required;
      }
      if (i.rule.maxLength) {
        rules[i.name].maxLength = maxLength(i.rule.maxLength);
      } 
      if (i.rule.minLength) {
        rules[i.name].minLength = minLength(i.rule.minLength);
      } 
    }
  });
  return rules;
};
const rules = getRules();
const v$ = useVuelidate(rules, formData);

插槽机制实现自定义表单项

Vue 的插槽机制允许我们在组件内部预留一些位置,让使用者可以自定义插入内容。在动态表单生成器中,我们可以使用具名插槽,让用户自定义表单的提交按钮或其他特定的表单项。例如:

xml 复制代码
<template>
  <form @submit.prevent="handleSubmit">
    <!-- 动态渲染表单项 -->
    <div v-for="item in formConfig" :key="item.name">
      <label :for="item.name">{{ item.name }}</label>
      <component :is="getComponent(item.type)"
        v-model="formData[item.name]"
        v-bind="item.props" 
      />
    </div>
    <slot name="submit">
      <button @click="handleSubmit">submit</button>
    </slot>
  </form>
</template>

实战演示

项目初始化

首先,我们需要创建一个新的 Vue 项目,并安装所需的依赖。可以使用 Vite 快速搭建项目:

kotlin 复制代码
npm init vite@latest dynamic-form -- --template vue
cd dynamic-form
npm install
npm install @vuelidate/core @vuelidate/validators

编写组件

1. DynamicForm.vue 组件

该组件是动态表单生成器的核心组件,负责解析 JSON 配置并渲染表单。同时,增加了表单数据提交和处理的逻辑。

xml 复制代码
<template>
  <form @submit.prevent=handleSubmit>
    
    <div v-for="item in formConfig" :key="item.name">
    
      <label :for="item.name">{{ item.name }}</label>
      <component :is="getComponent(item.type)"
      v-model="formData[item.name]"
      v-bind="item.props" 
      />
    </div>
    <slot name="submit">
      <button @click="handleSubmit">submit</button>
    </slot>
  </form>
</template>

<script setup>
import {reactive, ref}  from 'vue'
import { useVuelidate } from '@vuelidate/core'
import { required,maxLength } from '@vuelidate/validators'
import textRenderer from './TextRenderer.vue'
import selectRenderer from './SelectRenderer.vue'
import dateRenderer from './DateRenderer.vue'
const formData=reactive({})
const props = defineProps({
  formConfigJSON:{
    type:String,
    required:true,
    validator: (configJSON) =>{
      const config=JSON.parse(configJSON)
      return config.every((i)=>'name' in i && 'type' in i)
    }, 
  }
})

const formConfig=ref(JSON.parse(props.formConfigJSON))



const componentTypeMap={
  'text':textRenderer,
  'select':selectRenderer,
  'date-range':dateRenderer,
}
const getComponent=(type)=>{
  return componentTypeMap[type]
}

const handleSubmit = () => {
  v$.value.$validate();
  if (v$.value.$invalid) {
    console.log('表单验证不通过', v$.value.$errors);
    return;
  }
  console.log('Form submitted:', formData)

}

const getRules=()=>{
 const rules={}
 formConfig.value.forEach(i=>{
  if(i.rule){
    rules[i.name]={}
    if(i.rule.required==='required'){
      rules[i.name].required=required
    }
    if (i.rule.maxLength) {
      rules[i.name].maxLength = maxLength(i.rule.maxLength)
    } 
    if (i.rule.minLength) {
      rules[i.name].minLength = minLength(i.rule.minLength)
    } 
  }
  })
  console.log('Rules:', rules)
 return rules
}
const rules=getRules()
const v$ = useVuelidate(rules, formData);

</script>



<style scoped>
form {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

div {
  margin-bottom: 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

label {
  font-weight: 500;
  color: #333;
  font-size: 14px;
}

input, select {
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
  width: 100%;
  box-sizing: border-box;
}

input:focus, select:focus {
  outline: none;
  border-color: #409eff;
  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}

button {
  padding: 10px 20px;
  background-color: #409eff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: background-color 0.3s;
}

button:hover {
  background-color: #66b1ff;
}
</style>
2. TextRenderer.vue 组件

该组件用于渲染文本输入框。

xml 复制代码
<script setup>
defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
</script>
<template>
  <input type="text" :value="modelValue" @input="emit('update:modelValue', $event.target.value)">
</template>
<style scoped></style>
3. SelectRenderer.vue 组件

该组件用于渲染下拉选择框。

xml 复制代码
<script setup>
defineProps(['modelValue', 'selections']);
const emit = defineEmits(['update:modelValue']);
</script>
<template>
  <select :value="modelValue" @change="emit('update:modelValue', $event.target.value)">
    <option v-for="item in selections" :key="item.value" :value="item.value">{{ item.selection }}</option>
  </select>
</template>
<style scoped></style>

错误记录

  1. defineEmits 使用错误defineEmits 接收的参数应该是一个数组,这里没有使用数组包裹事件名。
  2. @select 事件使用错误 :在 <select> 元素上,通常使用 @change 事件来监听选择项的变化,而不是 @select
4. DateRenderer.vue 组件

该组件用于渲染日期选择框。

xml 复制代码
<script setup>
defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
</script>
<template>
  <input type="date" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
</template>
<style scoped></style>

使用动态表单组件

在 App.vue 中使用 DynamicForm 组件,并传入 JSON 配置。

xml 复制代码
<script setup>
import { ref } from 'vue';
import DynamicForm from './components/DynamicForm.vue';
const config = [
  {
    "name": "username",
    "type": "text",
    "rule": {"required": "required"},
  },
  {
    "name": "userid",
    "type": "text",
    "rule": {"required": "required", "maxLength": "6"},
  },
  {
    "name": "date",
    "type": "date-range",
    "rule": { "required": "required" },
  },
  {
    "name": "gender",
    "type": "select",
    "props": {
      "selections": [
        { "value": "male", "selection": "male" },
        { "value": "female", "selection": "female" }
      ]
    },
    "rule": { "required": "required" },
  }
];
const configJSON = JSON.stringify(config);
</script>
<template>
  <DynamicForm :formConfigJSON="configJSON"/>
</template>
<style scoped></style>

问题

@submit.prevent

在 Vue.js 中,@submit.prevent 是一个事件绑定语法。 @submit 用于监听 HTML 表单的 submit 事件,当用户提交表单(比如点击表单内的提交按钮)时会触发该事件。 .prevent 是一个修饰符,它的作用是调用 event.preventDefault() 方法,阻止表单的默认提交行为。在默认情况下,表单提交会导致页面刷新,使用 .prevent 修饰符后就可以避免页面刷新,从而让开发者可以在 JavaScript 中自定义处理表单提交的逻辑,比如通过 AJAX 发送表单数据到服务器等。 例如:

html 复制代码
<template> <form @submit.prevent="handleSubmit"> 
<input type="text" v-model="formData.name"> <input type="submit" value="提交"> 
</form> 
</template> 
<script> 
export default { 
data() 
{ return { formData: { name: '' } }; 
}, 
methods: { handleSubmit() { 
// 在这里处理表单提交的逻辑,比如发送数据到服务器 console.log('表单提交了', this.formData); } 
} 
}; 
</script> 

在上述代码中,@submit.prevent 会阻止表单的默认提交行为,然后调用 handleSubmit 方法来处理表单提交。

component标签是怎么用的

在不同的前端框架里, 标签的使用方式有所不同,下面主要介绍 Vue.js 和 React 中 标签的使用。

Vue.js 里 标签的使用

在 Vue.js 中, 标签是一个动态组件,可依据不同条件来渲染不同的组件。

基本用法

你可以借助 :is 绑定一个组件名或者组件选项对象,以此来动态渲染组件。

错误用法
arduino 复制代码
'text': 'InputRenderer', 
'select': 'SelectRenderer', 
'date-range': 'DateRangeRenderer'
解释
  • :is 绑定的 currentComponent 是一个变量,其值为组件名。

  • 当点击按钮时,currentComponent 的值会发生改变,从而动态渲染不同的组件。

input的type有哪些

v-bind绑定对象

v-bind="item.props"对象展开 写法,会自动将item.props对象中的每个属性绑定为组件的props。 完整语法是 v-bind:attributeName="expression"

ini 复制代码
    
<component 
  :is="getComponent(item.type)" 
  v-model="formData[item.name]" 
  v-bind="item.props" />
    其中item.props={
      selections: [
        { value: male, selection: male },
        { value: female, selection: female }
      ]
    }
  v-bind="item.props"等价于v-bind:selections=item.props.selections

总结

通过以上步骤,我们成功实现了一个 Vue 动态表单生成器。利用 Vue 的动态组件渲染、表单校验和插槽机制,我们可以根据不同的 JSON 配置动态生成表单,并对用户输入的数据进行校验。同时,增加了表单数据提交和处理的逻辑,模拟了将表单数据发送到后端的过程。这种方式大大提高了表单开发的灵活性和可维护性,减少了重复代码的编写。希望本文对你有所帮助,让你在前端开发中更加得心应手。

参考资料

相关推荐
shenyan~3 分钟前
关于 js:9. Node.js 后端相关
前端·javascript·node.js
uwvwko18 分钟前
ctfshow——web入门254~258
android·前端·web·ctf·反序列化
所待.38328 分钟前
深入解析SpringMVC:从入门到精通
前端·spring·mvc
逃逸线LOF1 小时前
CSS之精灵图(雪碧图)Sprites、字体图标
前端·css
海天胜景2 小时前
jqGrid冻结列错行问题,将冻结表格(悬浮表格)与 正常表格进行高度同步
前端
清风细雨_林木木2 小时前
解决 Tailwind CSS 代码冗余问题
前端·css
三天不学习3 小时前
VueUse/Core:提升Vue开发效率的实用工具库
前端·javascript·vue.js·vueuse
余道各努力,千里自同风3 小时前
CSS实现文本自动平衡text-wrap: balance
前端·css
Yvonne爱编码3 小时前
CSS- 4.3 绝对定位(position: absolute)&学校官网导航栏实例
前端·css·html·html5·hbuilder
繁依Fanyi4 小时前
ImgShrink:摄影暗房里的在线图片压缩工具开发记
开发语言·前端·codebuddy首席试玩官