vue3中如何更优雅地处理form组件

1,318 阅读3分钟

一、前言

在项目中用vue3有一段时间了,发现有一些代码,对form组件的处理不是很简洁,导致每次需求变更字段,都需要花费更多时间去处理,在这里分享一下比较好的写法。

二、form表单处理

2.1、需求场景假设

image.png

2.2、新增form表单

image.png

2.2.1、reactive和ref如何选择

我们希望表单中的每一个字段都具备响应式。

vue3中reactive和ref都可以手动进行声明,通常像这种需要传一个对象给后端的,用reactive比较合适。

如果使用ref的话,每次在script中改变值的时候,都需要每一个字段单独去改变,写法比较冗余;同理,如果使用reactive也还是单个去赋值的话

// ref冗余的写法
username.value = 'xxx'
password.value = 'xxx'
adress.value = 'xxx'

// reactive冗余的写法
const formState = reactive(
  {
    username: '',
    password: '',
    address: '',
  }
)
formState.username = 'xxx'
formState.password = 'xxx'
formState.adress = 'xxx'

这样的写法带来的一个后果是,后续每次增加或修改字段的时候。需要在每个用到的地方,针对每个值都改一变,比较容易出错。

2.2.2、如何对proxy对象赋值

当我们使用 const formState = reactive(xxxObject)的时候,返回的其实是一个proxy对象。

如果我们使用vue2的方式进行对象赋值this.formState = xxx,proxy对象的引用就改变了,则会丢失他的响应式,导致html中无法正常的改变表单值。

如果是单个字段,我们可以使用formState.username = 'xxx'这种方式进行处理。

多个字段的话,则可以使用Object.assign(formState, xxxObject)

2.2.3、如何重置对象

回想vue2中,我们在data函数中定义完一个对象后,则可以使用const { xxxObject } = this.$options.data(),来得到一个只有初始值的对象。

vue3的reactive似乎没有提供这么便捷的api。

但是问题不大,我们在定义对象的时候,像vue的data函数那样,把他定义成一个返回对象的函数即可。

const getFormState = () => {
  return {
    username: '',
    password: '',
    address: '',
  }
}
const formStateReactive = reactive(getFormState())
// 重置对象
Object.assign(formStateReactive, getFormState())

2.3、编辑form表单

编辑时,会把列表中某一行的数据,传到form表单中。
控制对话框显示/隐藏的字段visible可以使用ref。
某一行的数据curRowData,还是传proxy的对象过去。

/* 声明 start */
const props = defineProps({
  visible: {
    type: Boolean,
    default: false,
  },
  formType: {
    type: String,
    default: '',
  },
  curRowData: {
    type: Object,
    default: () => ({}),
  },
})

const getFormState = () => {
  return {
    username: '',
    password: '',
    address: '',
  }
}
const formStateReactive = reactive(getFormState())

// 在父组件使用ref的值,在子组件中需使用toRef来对props进行解构,保持响应式
const { visible, formType } = toRefs(props)
// 普通对象或者是proxy对象则直接解构即可
const { curRowData } = props
/* 声明 end */

// 当对话框显示时,且状态为【编辑】,则合并传过来的对象
watchEffect(() => {
  if (visible.value) {
    if (formType.value === 'edit') {
      Object.assign(formStateReactive, curRowData)
    }
  }
})

  • 在父组件使用ref的值,在子组件中需使用toRef来对props进行解构,保持响应式
  • 普通对象或者是proxy对象则直接解构即可

2.4、compositionAPI的使用场景

这时候产品经理加了需求,说要再增加一个表单,逻辑和之前的一致。只给他编辑用户名,其他字段都改为禁用的状态。
如果产品经理和需求都砍不掉的话,我们可以考虑把代码进行抽离,来更好地使用compositionAPI特性。

vue2中,基于optionsAPI的写法下,我们如果想组合一些重复的代码逻辑,Mixin是一个相对还可以的选择。但是Mixin也会带来很多来源混乱,导致bug难以排查的问题。

compositionAPI就舒服了。比如我要处理表单保存、校验相关的逻辑,就可以直接声明一个公共的js,form.js

// 这是一个公共的js

// 保存表单
export const saveForm = () => {}

// 校验表单
export const rules = () => {}

在用到的组件中直接import即可。

三、总结

不得不说,vue3的compositionAPI + script setup 写起来太爽了。不过写的时候,也要注意按业务逻辑,来抽离出不同的代码块。不然看起来会比较vue2的更乱,这样就违背了使用vue3的初衷了。