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

实现效果

需求分析

为啥有这个需求?

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

技术分析

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

动态渲染需要实现

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

难点

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

动态渲染组件

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

总结

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

相关推荐
linux_cfan9 小时前
videojs v10 源代码系列解读:06 · DOM 工具箱:事件、聚焦、Shadow DOM、定位
前端
尾善爱看海9 小时前
Vue 面试进阶篇:Composition API、插槽、自定义指令……8 个章节 + 高频面试题全解析
前端·javascript·vue.js·面试·vue
SEO_juper9 小时前
外贸多语言站最隐蔽的流量杀手:hreflang 错了,谷歌把德语页推给美国人(附审计脚本)
开发语言·前端·python·seo·独立站·谷歌优化
亿元程序员10 小时前
为什么现在 AI 这么发达了,还要坚持手搓教程?
前端
三小河10 小时前
在 Codex 桌面端接入 DeepSeek 模型(CC Switch 代理中转)
前端·javascript·人工智能
CoderYanger10 小时前
前端基础——JavaScript(WebAPI)代码案例
java·开发语言·前端·javascript·css·前端框架·html5
恋猫de小郭10 小时前
Shopify 从 React Native 回到 Swift/Kotlin,但是你以为有手就行??
android·前端·ios
Bs_MoneyMagnet11 小时前
基于springboot+vue的家居生活商城平台的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring
BillKu11 小时前
Element Plus 的 el-tab-pane:lazy 的说明
前端·javascript·vue.js
爱勇宝11 小时前
《道德经》第 11 章:真正有用的,常常是你没写出来的那部分
前端·后端·产品