动态处理复杂数据结构的表单

实现效果

需求分析

为啥有这个需求?

通过上一章实现我们实现了组件的封装,过了一些日子我们的组件日渐增多后,并且多次重复涉及时,我们应该如何快速开发呢。

技术分析

当我们有了这个需求,就该考虑如何实现了。

动态渲染需要实现

  • 如何挂载组件
  • 如何传递数据
  • 如何更新数据
  • 如何获取他们事件

难点

  • 组件来自不同位置 如果处理
  • 功能模块数据格式不一致 如何实现双向绑定

动态渲染组件

component:cn.vuejs.org/api/built-i...

实现

getComponent.ts

typescript 复制代码
 import { shallowReactive } from 'vue'
 ​
 import SnowInput from './components/Input.vue'
 import SnowSelect from './components/Select.vue'
 import SnowSelects from './components/Selects.vue'
 import SnowDatePicker from './components/DatePicker.vue'
 import SnowCascader from './components/Cascader.vue'
 ​
 const componentList: any = shallowReactive({
   SnowInput,
   SnowSelect,
   SnowSelects,
   SnowDatePicker,
   SnowCascader
 })
 ​
 export const getComponents = (componentName: string) => {
   componentName = 'Snow' + componentName.charAt(0).toUpperCase() + componentName.slice(1)
   return componentList[componentName]
 }
 ​

使用

Form.vue

xml 复制代码
 <script setup lang="ts">
 import { getComponents } from './getComponents'
 const name = 'input'
 </script>
 ​
 <template>
   <Component :is="getComponents(name)" />
 </template>
 <style lang="scss" scoped></style>
 ​

v-model:cn.vuejs.org/api/built-i...

到这里相信已经有所了解了接下来就开始直接实现吧

通过v-bind实现数据绑定

v-bind:cn.vuejs.org/api/built-i...

实现手动绑定

list是指向要查找的数据对象的key list是数组按照对象层级往下查找

typescript 复制代码
 // 实现手动查找
 const get = (list: any) => {
   // modelValue 是父组件传进表单需要渲染的数据
   let currentObj: any = modelValue.value
   if (typeof list === 'string') {
     return currentObj[list]
   }
 ​
   list.forEach((key: string) => {
     currentObj = currentObj[key] || ''
   })
 ​
   return currentObj
 }
 ​
 // 创建所需的对象格式
 const getProxy = (data: any) => {
   if (!data) return {}
   let obj: any = {}
   if (isString(data) || isObject(data)) {
     data = [data]
   }
   data.forEach((item: any) => {
     if (isString(item)) {
       obj['modelValue'] = get(item)
     } else {
       obj[item.key] = get(item.value)
     }
   })
   return obj
 }

使用手动绑定

xml 复制代码
 <script setup lang="ts">
 import { getComponents } from './getComponents'
 const name = 'input'
 </script>
 ​
 <template>
   <Component
     :is="getComponents(name)"
     v-bind="{
       ...getProxy(item.key),
       ...item.args
     }"
   />
 </template>
 <style lang="scss" scoped></style>
 ​

通过v-on实现数据更新

v-on:cn.vuejs.org/api/built-i...

实现手动更新

typescript 复制代码
 const set = (newValue: any, path: any) => {
   if (typeof path === 'string') {
     modelValue.value[path] = newValue
     return
   }
   let currentObj = modelValue.value
 ​
   for (const key of path) {
     if (path[path.length - 1] === key) {
       currentObj[key] = newValue
       return
     }
 ​
     currentObj = currentObj[key] || ''
   }
 }
 ​
 const setProxy = (modelValue: any, customEvent?: any) => {
   let modelValues = setModelValues(modelValue)
   let customEvents = setEvents(customEvent)
   return {
     ...modelValues,
     ...customEvents
   }
 }
 ​
 const setEvents = (events: any) => {
   if (!events) return {}
   if (isString(events) || isObject(events)) {
     events = [events]
   }
 ​
   let obj: any = {}
 ​
   events.forEach((item: any) => {
     if (isString(item)) {
       obj[events] = (data: any) => {
         emit(`${events}`, data)
       }
     } else {
       obj[item.key] = (data: any) => {
         emit(item.value, data)
       }
     }
   })
   return obj
 }
 ​
 const setModelValues = (data: any) => {
   if (!data) return
   if (isString(data) || isObject(data)) {
     data = [data]
   }
 ​
   let obj: any = {}
   data.forEach((item: Item) => {
     if (isString(item)) {
       obj['update:modelValue'] = (newValue: any) => {
         set(newValue, item)
       }
     } else {
       obj[`update:${item.key}`] = (newValue: any) => {
         set(newValue, item.value)
       }
     }
   })
   return obj
 }

使用手动绑定

xml 复制代码
 <script setup lang="ts">
 import { getComponents } from './getComponents'
 const name = 'input'
 </script>
 ​
 <template>
   <Component
     :is="getComponents(name)"
     v-on="setProxy(item.key, item.event)"
     v-bind="{
       ...getProxy(item.key),
       ...item.args
     }"
   />
 </template>
 <style lang="scss" scoped></style>
 ​

扩展

这时我们就最小实现双向绑定数据了,可以尝试通过上述函数来实现更多功能。

例如有个场景,需要某个指定值满足xxx才显示。

实现校验数据

kotlin 复制代码
 const validateValue = (data: any): boolean => {
   if (isNull(data)) return true
 ​
   // 严格模式
   if (data?.strict) {
     return data.value === get(data.target)
   }
 ​
   return data.value == get(data.target)
 }

使用校验数据

ini 复制代码
 <script setup lang="ts">
 import { getComponents } from './getComponents'
 const name = 'input'
 </script>
 ​
 <template>
   <Component
     v-if="validateValue(item?.validate)"
     :is="getComponents(name)"
     v-on="setProxy(item.key, item.event)"
     v-bind="{
       ...getProxy(item.key),
       ...item.args
     }"
   />
 </template>
 <style lang="scss" scoped></style>
 ​

总结

感觉没啥人看,就偷懒省掉注释了~ 主要等会就要干饭了 后续有需求在更新吧 祝大家周末愉快

相关推荐
LFly_ice31 分钟前
学习React-22-Zustand
前端·学习·react.js
东华帝君1 小时前
vue3自定义v-model
前端
JIngJaneIL1 小时前
远程在线诊疗|在线诊疗|基于java和小程序的在线诊疗系统小程序设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·小程序·毕设·在线诊疗小程序
fruge1 小时前
搭建个人博客 / 简历网站:从设计到部署的全流程(含响应式适配)
前端
光影少年1 小时前
css影响性能及优化方案都有哪些
前端·css
呆呆敲代码的小Y2 小时前
2025年多家海外代理IP实战案例横向测评,挑选适合自己的
前端·产品
q***3752 小时前
爬虫学习 01 Web Scraper的使用
前端·爬虫·学习
v***5652 小时前
Spring Cloud Gateway
android·前端·后端
b***59432 小时前
分布式WEB应用中会话管理的变迁之路
前端·分布式
q***21602 小时前
Spring Boot项目接收前端参数的11种方式
前端·spring boot·后端