【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

相关推荐
王夏奇15 分钟前
python中的__all__ 具体用法
java·前端·python
大家的林语冰1 小时前
《前端周刊》尤大开源 Vite+ 全家桶,前端工业革命启动;尤大爆料 Void 云服务新产品,Vite 进军全栈开发;ECMA 源码映射规范......
前端·javascript·vue.js
jiayong231 小时前
第 8 课:开始引入组合式函数
前端·javascript·学习
田八1 小时前
聊聊AI的发展史,AI的爆发并不是偶然
前端·人工智能·程序员
zhanghongbin011 小时前
AI 采集器:Claude Code、OpenAI、LiteLLM 监控
java·前端·人工智能
IT_陈寒2 小时前
Python的列表推导式里藏了个坑,差点让我加班到凌晨
前端·人工智能·后端
吴声子夜歌2 小时前
ES6——正则的扩展详解
前端·mysql·es6
天***88522 小时前
Edge 浏览器离线绿色增强版+官方安装包,支持win7等系统
前端·edge
漫游的渔夫2 小时前
别再直接 `json.loads` 了!AI 返回的 JSON 坑位指南
前端·人工智能
软件工程师文艺2 小时前
从0到1:Claude Code如何用React构建CLI应用
前端·react.js·前端框架