【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

相关推荐
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
沈梦研5 小时前
【Vscode】Vscode不能执行vue脚本的原因及解决方法
ide·vue.js·vscode
hunter2062065 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb5 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角5 小时前
CSS 颜色
前端·css
轻口味6 小时前
Vue.js 组件之间的通信模式
vue.js
浪浪山小白兔6 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579657 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me7 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架