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

16 阅读2分钟

实现效果

111.gif

需求分析

为啥有这个需求?

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

技术分析

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

动态渲染需要实现

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

难点

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

动态渲染组件

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

实现

getComponent.ts

 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

 <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是数组按照对象层级往下查找

 // 实现手动查找
 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
 }

使用手动绑定

 <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…

实现手动更新

 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
 }

使用手动绑定

 <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才显示。

实现校验数据

 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)
 }

使用校验数据

 <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>
 ​

总结

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