【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

相关推荐
gongzemin5 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox18 分钟前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
麦麦大数据40 分钟前
neo4j+django+deepseek知识图谱学习系统对接前后端分离前端vue
vue.js·django·知识图谱·neo4j·deepseek·在线学习系统
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼1 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下1 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758102 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox