v3中reactive中的小坑
“现在我们能造什么?能造桌子椅子,能造茶碗茶壶,能种粮食,还能磨成面粉,还能造纸,但是,一辆汽车、一架飞机、一辆坦克、一辆拖拉机都不能造。”
前两天在写一个比较痛苦的表单,大概是这种: 抽屉内的表单在点击不同radio时会显示不同的待选项,抽屉关闭时所有项要回到初始态。保存时要分别校验不同项下的内容,基础校验是不能为空。
也同之前一样在使用vue3的ref
小坑:关于reactive
const formData = ref({
name: ``,
type: 0,
// files: [],
'base-text-diff': {
baseFileOnly: []
},
'model-text-diff': {
modelFileOnly: [], //单
diffedFileList: []
},
'appoint-text-diff': {
specialList: [], //特定页文件
beforePointFileOnly: [], //特定页,指定前
afterPointFileOnly: [] //特定页,指定后
},
pointPageType: `specialPage`, // 特定页
diffPageType: [], // 比对页:目录封面印章
// 范文页码
specialPage1_start: 1,
specialPage1_end: 1,
// 比对页码
specialPage2_start: 1,
specialPage2_end: 1
})
由于表单太过复杂,且判断较多,于是摒弃了使用ui的表单校验方式,改为手动重置:
// 当修改任务类型后,重置文件列表
watch(
() => formData.value.type,
(n, o) => {
resetTaskType(n, o)
resetFileList()
}
)
const resetTaskType = (newval, oldval) => {
// console.log('oldval :>> ', oldval)
if (oldval === 2) {
formData.value.pointPageType = `specialPage`
formData.value.diffPageType = []
formData.value.specialPage1_start = 1
formData.value.specialPage1_end = 1
formData.value.specialPage2_start = 1
formData.value.specialPage2_end = 1
}
resetFileList()
}
// 重置所有绑定的文件数据
const resetFileList = () => {
formData.value.['base-text-diff'].baseFileOnly = []
formData.value.['model-text-diff'].modelFileOnly = []
formData.value.['model-text-diff'].diffedFileList = []
formData.value.['appoint-text-diff'].specialList = []
formData.value.['appoint-text-diff'].beforePointFileOnly = []
formData.value.['appoint-text-diff'].afterPointFileOnly = []
}
原本以为这样就ok了,毕竟虽然麻烦但很简单。但是发现它报错了,没有截图,总之就是报了个奇怪的错误
后来就尝试使用reactive声明变量。
第一次有机会使用,说不激动是不可能的,但是在我看来也就仅仅是将value
去掉而已,但是在重置此变量时告警:reactive声明的是常量,无法更改
欺天啦!我声明的是const不假 但是reactive不是用来声明响应式变量的吗??
退一步越想越气,于是翻了翻官方文档:
那该怎么做?
后来翻了翻解决方案,发现一般都是使用此方案:
// 本质上是遍历初始数据后将初始数据重新赋予reactive数据
声明初始化数据:
const baseForm = () => ({
name: ``,
type: 0,
// files: [],
'base-text-diff': {
baseFileOnly: []
},
'model-text-diff': {
modelFileOnly: [], //单
diffedFileList: []
},
'appoint-text-diff': {
specialList: [], //特定页文件
beforePointFileOnly: [], //特定页,指定前
afterPointFileOnly: [] //特定页,指定后
},
pointPageType: `specialPage`, // 特定页
diffPageType: [], // 比对页:目录封面印章
// 范文页码
specialPage1_start: 1,
specialPage1_end: 1,
// 比对页码
specialPage2_start: 1,
specialPage2_end: 1
})
//声明reactive变量:
const formData = reactive(baseForm())
// 重置reactive变量
const resetFormData = () => {
Object.keys(baseForm()).forEach((key) => {
formData[key] = baseForm()[key]
})
}
如何校验非表单数据?
上面说摒弃了校验表单,因而要自己实现校验功能,由于不同类型要有不同的校验数据,因而有了以下方式 :
// 点击抽屉的确认按钮
const confirm = () => {
const checkError = valiteForm()
}
// 校验
const valiteForm = () => {
if (!formData.name) {
return { msg: `请输入用户名称` }
}
switch (formData.type) {
// 文本 对比
case 0:
if (!formData['base-text-diff'].baseFileOnly.length) {
return { msg: `未上传文本比对类型文件,请确认` }
}
break
case 1:
if (
!formData['model-text-diff'].modelFileOnly.length ||
!formData['model-text-diff'].diffedFileList.length
) {
const msgType = !formData['model-text-diff'].modelFileOnly.length ? `范文` : `比对`
return { msg: `未上传范本比对类型的${msgType}文件,请确认` }
}
break
// 指定页
case 2:
if (formData.pointPageType === `specialPage`) {
if (!formData.diffPageType.length) {
return { msg: `未选择特定页比对中的 比对页,请确认` }
}
if (!formData['appoint-text-diff'].specialList.length) {
return { msg: `未上传特定页比对中的比对文件,请确认` }
}
}
if (formData.pointPageType === 'pointPageNum') {
if (formData.specialPage1_end < formData.specialPage1_start) {
return { msg: `特定页比对中的范文页页码有误,请确认` }
}
if (formData.specialPage2_end < formData.specialPage2_start) {
return { msg: `特定页比对中的比对页页码有误,请确认` }
}
if (
!formData[`appoint-text-diff`].beforePointFileOnly.length ||
!formData[`appoint-text-diff`].afterPointFileOnly.length
) {
const msgType = !formData[`appoint-text-diff`].beforePointFileOnly.length
? '范文'
: '比对'
return { msg: `未上传特定页比对中的${msgType}文件,请确认` }
}
}
break
default:
break
}
}
//校验未通过时,会返回一个object,否则返回undefind,因而:
(//校验的方式多种多样,只需让用户感知有误即可,我们甚至可以模仿表单项下手动加一个警示语句如
)
const confirm = () => {
const checkError = valiteForm()
if (checkError) {
return MessagePlugin.warning(checkError.msg)
}
}
原本写到这里,只需将formdata
数据返回给父组件然后再调用重置方法即可,然而还是有坑:
reactive异步问题
查看以下代码:
const confirm = () => {
const checkError = valiteForm()
if (checkError) {
return MessagePlugin.warning(checkError.msg)
}
emit('success', formData)
emit('update:visible', false)
resetFormData()
}
逻辑是发送父组件当前数据,随后关闭抽屉,随后重置表单,但是这样写是有误的
,表现是发给父级的是空数据,这是由于重置方法是同步的,而emit则是异步的,原以为只会出现在reacthook
中的问题而今出现在了vue中,因而又有了以下解决方案:
// 实测此方式可大幅减少包体积
import cloneDeep from 'lodash/cloneDeep'
const confirm = () => {
const checkError = valiteForm()
if (checkError) {
return MessagePlugin.warning(checkError.msg)
}
// 预先拷贝一份,同步
const formDataCopy = cloneDeep(formData)
//此时formDataCopy为用户填入数据
emit('success', formDataCopy)
emit('update:visible', false)
resetFormData()
}
至此坑就填完了,不过reactive在官方文档中已明确提示不建议继续使用,不明白的是为什么不可以使用ref.value[property]
方式赋值,埋坑
以上。