【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

相关推荐
bin915341 分钟前
DeepSeek 助力 Vue 开发:打造丝滑的复制到剪贴板(Copy to Clipboard)
前端·javascript·vue.js·ecmascript·deepseek
晴空万里藏片云2 小时前
elment Table多级表头固定列后,合计行错位显示问题解决
前端·javascript·vue.js
曦月合一2 小时前
html中iframe标签 隐藏滚动条
前端·html·iframe
奶球不是球2 小时前
el-button按钮的loading状态设置
前端·javascript
kidding7232 小时前
前端VUE3的面试题
前端·typescript·compositionapi·fragment·teleport·suspense
Σίσυφος19004 小时前
halcon 条形码、二维码识别、opencv识别
前端·数据库
学代码的小前端4 小时前
0基础学前端-----CSS DAY13
前端·css
css趣多多6 小时前
案例自定义tabBar
前端
姑苏洛言7 小时前
DeepSeek写微信转盘小程序需求文档,这不比产品经理强?
前端
林的快手7 小时前
CSS列表属性
前端·javascript·css·ajax·firefox·html5·safari