vue3使用useVmode简化组件通信

在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 />
相关推荐
爱编程的喵几秒前
React 19 + Vite 6 构建现代化旅行应用智旅(1)
前端·react.js
l1t5 分钟前
使用流式函数解决v语言zstd程序解压缩失败问题
前端·压缩·v语言·zstd
小离a_a16 分钟前
el-tree方法的整理
前端·vue.js·elementui
90后的晨仔24 分钟前
Vercel部署完全指南:从踩坑到成功的实战经验分享
前端·vue.js
泯泷1 小时前
Tiptap 深度教程(三):核心扩展全面指南
前端·javascript·全栈
前端AK君1 小时前
rolldown-vite初体验
前端·前端工程化
zayyo1 小时前
大厂前端为什么都爱用pnpm + monorepo 做项目工程化架构?
前端
桃桃乌龙_95271 小时前
受不了了,webpack3.x升级到webpack4.x
前端·webpack
青花雅月1 小时前
解决复用页面只是接口不同的问题的完整指南
前端
FogLetter1 小时前
前端组件通信新姿势:用mitt实现Toast组件的优雅通信
前端·react.js