【vue基础】如何实现一个表单组件

如果我们需要传给子组件一个 formData 对象,并且子组件可以进行编辑,最后子组件完成修改时提醒父组件,如何实现?

JavaScript 复制代码
const formData=reactive({
	name:'zhangsan',
	age:23,
	city:'beijing',
})

v-model

  1. 将 formData 的属性逐个双向绑定:
html 复制代码
// Father.vue
<Child v-model:name="formData.name" v-model:age="formData.age" v-model:city="formData.city"/>

// Child.vue
<input :value="name" @input="$emit('update:name',$event.target.value)"/>
<input :value="age" @input="$emit('update:age',$event.target.value)"/>
<input :value="city" @input="$emit('update:city',$event.target.value)"/>

<script setup>
defineProps(['age','name','city'])
defineEmits(['update:age','update:name','update:city'])
</script>
  1. 可以看出,有多少个属性就要写多少个。使用 computed 进行简化。 最简单的 computed。让 input 上只绑定一个 v-model。
JavaScript 复制代码
<input @v-model="name">

const props = defineProps(['age', 'name', 'city'])
const emits = defineEmits(['update:age', 'update:name', 'update:city', 'submit'])
const name=computed({
  get() {
    return props.name
  },
  set(value) {
    emit('update:name', value)
  }
})

将多个 computed 加到 myModel 对象上。这里使用 Reflect 避免读取对象的属性时 ts 报错。

JavaScript 复制代码
<template>
  <div>
  // 模板部分更简洁
    <input v-model="myModel.name" />
    <input v-model="myModel.age" />
    <input v-model="myModel.city" />
    <button @click="$emit('submit')"></button>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, reactive } from 'vue';

// 新增数据只需修改这一对象
const data = { age: '', name: '', city: '' }

// 下面代码不再需要修改
const myModel = reactive(data)
const props = defineProps(Object.keys(data))
const emits = defineEmits([...Object.keys(data).map(key=>'update:'+key), 'submit'])

onMounted(() => {
  Reflect.ownKeys(props).forEach(key => {
    Reflect.set(myModel, key, computed({
      get() {
        return Reflect.get(props, key)
      },
      set(value) {
        emits('update:' + String(key) as any, value)
      }
    }))
  })
  console.log(Reflect.ownKeys(props))
})
</script>

然而,父组件使用该组件时需要传入大量属性:

JavaScript 复制代码
// Father.vue
<Child v-model:name="formData.name" v-model:age="formData.age" v-model:city="formData.city"/>

v-bind

实际上,可以直接 v-bind 一个对象,并且不会有警告,但这种写法是违背单向数据流的(尽管 element 组件库也是这样写的)

html 复制代码
// Father.vue
<Child :form="formData"/>

// Child.vue
<template>
  <div>
    <input v-model="form.name" />
    <input v-model="form.age" />
    <input v-model="form.city" />
    <button @click="$emit('submit')"></button>
  </div>
</template>
<script setup lang='ts'>
defineProps(['form'])
defineEmits(['submit'])
</script>

子组件维护

如果改变需求,让子组件维护表单呢?

  1. 父组件不传参。
  2. 子组件内部维护一个 formData,负责修改和提交表单。

不足:父组件无法得知子组件的状态。 解决办法:

  1. 子组件定义一个提交事件,将 formData 作为参数提交即可。父组件只需绑定提交事件,就可以获得数据。
  2. 子组件在维护表单数据的同时,向 store 同步,其它组件通过 store 读取表单的数据。不推荐,因为引入 store 增加了维护的难度,除非确实要在多处位置共享这一表单数据。

总结

参考 element 组件库的 form 组件,我们不难得出结论,就是使用 v-bind 来绑定我们的 formData,在子组件内部对传入的 formData 的属性进行修改,并通过事件通知父组件。在父组件中提交表单。这样,子组件的职责就只是单纯的表单编辑功能,不包含执行表单提交调用 api 的义务。尽管使用 v-bind 完成了并不是它初衷的任务(使用单向数据流的指令却实现数据的双向绑定),但对父组件还是子组件来说, ,这是代码量最少的方法了,而在这里,代码量少,意味这更高的可维护性。

参考

组件 v-model | Vue.js

相关推荐
IT_陈寒17 分钟前
Redis这个内存杀手,差点让我们运维半夜追杀我
前端·人工智能·后端
子兮曰43 分钟前
DeepSeek TUI:原生 Rust 打造的终端 AI 编码 Agent
前端·javascript·后端
DFT计算杂谈1 小时前
自动化脚本一键绘制三元化合物相图
java·运维·服务器·开发语言·前端·python·自动化
子兮曰1 小时前
深入 Superpowers:180k Stars 的开源 AI 编程方法论是如何工作的
前端·javascript·后端
沸点小助手1 小时前
「新晋AI顶流PK:GPT-5.5 vs DeepSeek V4&掘友吐槽小会」沸点获奖名单公示|本周互动话题上新🎊
前端·人工智能
不会敲代码11 小时前
从零到一:用 Vue3 + Kimi 大模型打造「拍照记单词」AI 应用
vue.js·typescript·aigc
隔壁的大叔1 小时前
Markdown 渲染如何穿插自定义组件
前端·javascript·vue.js
Rik1 小时前
用 AI Skill 封装你的工作流:从代码规范到全流程提效实战
前端·后端
Dabei2 小时前
Android TV 焦点处理详解:遥控器与空鼠
android·前端
RONIN2 小时前
脚手架搭建项目框架(create-vite、vue-cli、create-vue、quasar-cli)
vue.js