在vue3中我们知道v-mode是一个语法糖,他是由:modelValue和@update:modelValue组合成的,基于此思考怎么简化表单组件之前的数据通信
场景: 我们在实际项目中往往会有一个项目中有很多弹窗里面展示不同表单的场景,不同的按钮展示的表单可能都不一样,这样你需要把表单拆成一个个组件来优化结构.这样每个表单的结构虽然我们可以梳理清晰,但是带来的思考就是每个表单中的数据都是需要在弹窗组件中进行逻辑处理的,这样就需要弹窗组件和表单组件的数据通信,我们每个表单可能都需要去监听表单数据变化,来实时修改父组件中的数据,
ts
<el-drawer
v-model="dialogVisible"
:close-on-click-modal="false"
@close="handleDrawerClose"
size="50%"
style="font-weight: 500"
:title="dialogTitle"
>
<!-- <el-divider style="margin-top: 0" /> -->
<div style="margin-top: 50px">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="130px"
>
//表单项
xxxxxxxxxxxxxx
</el-form>
</div>
<template #footer>
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button :disabled="formLoading" type="primary" @click="submitForm">保存</el-button>
</template>
</el-drawer>
watch(flowPermission1, (flow) => {
if (flow.flag && flow.id === _uid) {
emits('update:flowPermission', flow.value)
}
})
watch(approverConfig1, (approver) => {
if (approver.flag && approver.id === _uid) {
emits('update:nodeConfig', approver.value)
}
})
watch(copyerConfig1, (copyer) => {
if (copyer.flag && copyer.id === _uid) {
emits('update:nodeConfig', copyer.value)
}
})
watch(conditionsConfig1, (condition) => {
if (condition.flag && condition.id === _uid) {
emits('update:nodeConfig', condition.value)
}
})
这种代码对于你后期维护,简直是不要太酸爽,一堆的监听和事件调用看的头皮发麻
所以我在思考能不能简化数据之间的交互过程,我只需要管父组件定义一个接受表单数据的变量,表单组件只需要拿到我传过去的对象,绑定表单自动收集
开始分析,为什么v-model绑定表单元素能直接接受到表单项修改的值列如:
ts
<el-input v-model="input" style="width: 240px" placeholder="Please input" />
那我是不是可以通过;propsName
&@update:propsName
这种形式来实现数据通信
ts
<Father>
<Childe v-model="formData"/>
</Father>
const formData = ref({})
//=============== Childe
<template>
<el-form
:label-position="labelPosition"
label-width="auto"
:model="formLabelAlign"
style="max-width: 600px"
>
</el-form-item>
<el-form-item label="Name" :label-position="itemLabelPosition">
<el-input v-model="xxx />
</el-form-item>
<el-form-item label="Activity zone" :label-position="itemLabelPosition">
<el-input v-model="xxx" />
</el-form-item>
<el-form-item label="Activity form" :label-position="itemLabelPosition">
<el-input v-model="xxx" />
</el-form-item>
</el-form>
</template>
const props = definProps<{
modeValue:any
}>()
可能到这里有人会疑惑,难道这里就可以直接使用modelValue直接绑定表单然后修改?当然不是,直接修改props的数据相当于打破单项数据流,到时候你去进行数据溯源的时候你就头疼了,我们vue开发的宗旨就是保证数据的单项数据流,那接下来该怎么办呢?
我们可以通过计算数据来包一层,既然你不能直接修改props中的数据,那我将这个数据使用computed
包一层再去修改计算属性不就行了
ts
<template>
<el-form
:label-position="labelPosition"
label-width="auto"
:model="computedForm"
style="max-width: 600px"
>
</el-form-item>
<el-form-item label="name" :label-position="itemLabelPosition">
<el-input v-model="computedForm.name />
</el-form-item>
<el-form-item label="age" :label-position="itemLabelPosition">
<el-input v-model="computedForm.age" />
</el-form-item>
<el-form-item label="sex" :label-position="itemLabelPosition">
<el-input v-model="computedForm.sex" />
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
const props = definProps<{
modeValue:any
}>()
const emit = definEmis(['update:modelvalue'])
const computedForm = computed({
get(){
return props.modelValue
},
set(val){
emit('update:modelvalue',val)
}
})
<script />
现在看上去是可行,我们能直接读取计算属性上的属性,但是当我们去修改表单项时发现,计算属性并没有生效,这是应为我们此时计算属性返回的是一个对象,你需要修改这个对象的引用才能检测到变化,思考怎么去监测对象属性变化: proxy能监听对象的基本行为
ts
<template>
<el-form
:label-position="labelPosition"
label-width="auto"
:model="computedForm"
style="max-width: 600px"
>
</el-form-item>
<el-form-item label="name" :label-position="itemLabelPosition">
<el-input v-model="computedForm.name />
</el-form-item>
<el-form-item label="age" :label-position="itemLabelPosition">
<el-input v-model="computedForm.age" />
</el-form-item>
<el-form-item label="sex" :label-position="itemLabelPosition">
<el-input v-model="computedForm.sex" />
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
const props = definProps<{
modeValue:any
}>()
const emit = definEmis(['update:modelvalue'])
const computedForm = computed({
get(){
return new Proxy(props.modeValue,{
set(target, key, value, receiver){
emit('update:modelvalue',{
...target,
key:value
})
return Reflect.set(...arguments)
}
})
},
set(val){
emit('update:modelvalue',val)
}
})
<script />
这样我们就能检测到表单项的修改了,我们将这段逻辑封装成一个通用的hooks
ts
import { computed } from 'vue'
export function useVModel(prop: any, propName: string, emit: any) {
return computed({
get() {
return new Proxy(prop[propName], {
set(target: object, name: string, val: any) {
// console.log('get', target, name, val, propName, prop)
emit('update:' + propName, {
...target,
[name]: val
})
return true
}
})
},
set(val) {
console.log('set', val)
emit('update:' + propName, val)
}
})
}
完结撒花,在vueuse
中也有对应的hooks useVmode,useVmodels
感兴趣的可以去看看他们的实现方法
在项目中使用:
ts
//父组件中
<CurrentForm v-mode="formData"/>
const formData = ref({})
//===========================
//子组件(表单组件)
<template>
<el-form
:label-position="labelPosition"
label-width="auto"
:model="proxyFormData"
style="max-width: 600px"
>
</el-form-item>
<el-form-item label="name" :label-position="itemLabelPosition">
<el-input v-model="proxyFormData.name />
</el-form-item>
<el-form-item label="age" :label-position="itemLabelPosition">
<el-input v-model="proxyFormData.age" />
</el-form-item>
<el-form-item label="sex" :label-position="itemLabelPosition">
<el-input v-model="proxyFormData.sex" />
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
const props = definProps<{
modeValue:any
}>()
const emit = definEmis(['update:modelvalue'])
const proxyFormData = useVModel(props, 'modelValue', emit) as any
<script />