配置式表单
原理,在my-form.vue
组件里面遍历schema来渲染表单控件,formData通过props传递进来,这个组件只负责渲染数据。 创建use-form.tsx
在这个组合式函数里面导入myForm,通过defineComponent
创建组件并返回,然后在useForm这个函数里面管理表单状态, 最终返回Form组件和formApi
代码
vue
<script setup lang="ts">
import { ref } from 'vue'
import type { FormOptions } from './types'
import { useForm } from './use-form'
const formProps: FormOptions = {
schema: [
{
component: 'Input',
label: '用户名',
fieldName: 'username',
defaultValue: '测试'
}
]
}
const [MyForm, formApi] = useForm(formProps)
const reset = () => {
formApi.setState({
schema: [
{
component: 'Input',
label: '重新渲染',
fieldName: 'username',
defaultValue: '11111111111111'
}
]
})
}
const text = ref('')
const update = () => {
formApi.setValues({
username: text.value
})
}
const getValue = () => {
const value = formApi.getValues<{
username: string
}>()
console.log('获取到的值', value)
value.username = '看看是否影响原表单'
}
</script>
<template>
<div>
复杂表单
<a-button @click="reset">重新渲染</a-button>
<div>
<a-input v-model:value="text" />
<a-button @click="update">修改表单值</a-button>
</div>
<a-button @click="getValue">获取值</a-button>
<MyForm />
</div>
</template>
tsx
import { defineComponent, unref, ref, shallowRef, toRaw, onMounted } from 'vue'
import type { MaybeRef } from 'vue'
import type { FormOptions } from './types'
import MyForm from './my-form.vue'
import { cloneDeep } from 'lodash-es'
export const useForm = (formOptions: MaybeRef<FormOptions>) => {
const formKey = ref(false)
const __formOptions = shallowRef<FormOptions>(unref(formOptions))
const __formData = ref<Record<string, any>>({})
// 解构schema
const initForm = (options: MaybeRef<FormOptions>) => {
__formOptions.value = unref(options)
// 获取schema中字段的初始值
const defaultValues: Record<string, any> = {}
__formOptions.value.schema.forEach(formItem => {
defaultValues[formItem.fieldName] = toRaw(formItem?.defaultValue)
})
__formData.value = defaultValues
}
onMounted(() => {
initForm(formOptions)
})
const Form = defineComponent(() => {
const update = (data: Record<string, any>) => {
__formData.value = data
}
return () => (
<>
<MyForm
key={formKey.value as boolean}
{...__formOptions.value}
modelValue={__formData.value}
onUpdate:modelValue={update}
/>
</>
)
})
const formApi = {
setState: (options: MaybeRef<FormOptions>) => {
initForm(options)
// 确保form组件重新渲染
formKey.value = !formKey.value
},
getValues: <T extends Record<string, any>>(): T => {
return cloneDeep(toRaw(__formData.value)) as T
},
setValues: (value: MaybeRef<Record<string, any>>) => {
__formData.value = cloneDeep(toRaw(unref(value)))
},
}
return [Form, formApi] as const
}
vue
<script setup lang="ts">
import { onMounted } from 'vue'
import type { FormOptions } from './types'
import { globalState } from './global-state'
defineProps<FormOptions>()
const formData = defineModel<Record<string, any>>()
onMounted(() => {
console.log('重新渲染')
})
</script>
<template>
<a-form v-model:model="formData">
<template v-for="(item, key) in schema" :key="key">
<a-form-item :label="item.label" :name="item.fieldName">
<component
:is="globalState.getComponents(item.component)"
v-model:value="formData[item.fieldName]"
/>
</a-form-item>
</template>
</a-form>
</template>
ts
import { Input, Select } from 'ant-design-vue'
// 所有需要用到的表单控件都需要提前在这里导入
const _components = {
Input,
Select
} as const;
type ComponentType = keyof (typeof _components);
type ComponentMap = {
[key in ComponentType]: typeof _components[key]
}
export class GlobalState {
// 所有需要用到的表单控件
#Components: ComponentMap = _components
public getComponents(
componentName?: ComponentType,
) {
if (componentName) {
return this.#Components[componentName]
}
return this.#Components
}
}
export const globalState = new GlobalState()
ts
import type { Component } from 'vue'
type ComponentType =
| 'Input'
| 'Select'
export interface FormOptions {
schema: {
/** 组件 */
component: Component | ComponentType
/** 组件参数 */
// componentProps?: ComponentProps;
componentProps?: any
/** 默认值 */
defaultValue?: any
/** 依赖 */
// dependencies?: FormItemDependencies;
/** 描述 */
description?: string
/** 字段名 */
fieldName: string
/** 帮助信息 */
help?: string
/** 表单项 */
label?: string
// 自定义组件内部渲染
// renderComponentContent?: RenderComponentContentType;
/** 字段规则 */
// rules?: FormSchemaRuleType;
/** 后缀 */
// suffix?: CustomRenderType;
}[]
}