defineFatForm 定义器
和 defineFatTable
类似, 我们也提供了 defineFatForm 用于快速定义表单。相比直接使用 <template>
, defineFatForm 可以提供更好的类型提示和检查。
Hello world
查看代码
tsx
import { defineFatForm } from '@wakeadmin/components';
import { ElMessageBox } from 'element-plus';
export default defineFatForm<{
// 🔴 这里的泛型变量可以定义表单数据结构
name: string;
nickName: string;
}>(({ item, form, consumer, group }) => {
// 🔴 这里可以放置 Hooks
// 🔴 form 为 FatForm 实例引用
console.log(form);
// 返回表单定义
return () => ({
// FatForm props 定义
initialValue: {
name: 'ivan',
nickName: '狗蛋',
},
submit: async values => {
await ElMessageBox.confirm('确认保存');
console.log('保存成功', values);
},
// 🔴 子节点
children: [
item({ prop: 'name', label: '账号名' }),
item({
prop: 'nickName',
label: '昵称',
}),
// 🔴 这里甚至可以放 JSX
<div>JSX hello</div>,
// 🔴 不过,如果你想要监听 表单数据,还是建议使用 FatFormConsumer, 否则会导致整个表单的重新渲染
// 不信,你可以打开 Vue 开发者工具的 Highlight Updates 试一下
consumer(({ values }) => {
return group({
label: '表单状态',
children: (
<pre>
<code>{JSON.stringify(values, null, 2)}</code>
</pre>
),
});
}),
],
});
});
defineFatForm
使用方法类似于 Vue 的 defineComponent
, 只不过返回一个 DSL
, 这个 DSL 定义了表单的结构和配置信息。
定义结构
defineFatForm 的 DSL
需要满足类似 <template>
的灵活结构的同时,保持一定约束性。为此我们提供了一些 helpers
来辅助你定义这个 DSL,并且能关联类型上下文。
group
定义FatFormGroup
item
定义FatFormItem
section
定义FatFormSection
consumer
定义FatFormConsumer
table
定义FatFormTable
tableColumn
定义FatFormTable#columns
子项
底层方法
- renderChild 可以将上述辅助转换为 JSX 节点
- renderChildren 和 renderChild 类似,只不过支持接收一个数组
上述 helper
接收对应组件的 Props,除了 item
, group
、section
还支持传入一个 children
字段, 用于定义下级节点:
tsx
defineFatForm(({ item, group, section, consumer }) => {
return () => ({
// 这里放置 FatForm 的 Props
// ...
// 定义下级节点
children: [
group({
children: [
// group 支持下级节点
],
}),
section({
children: [
// section 支持下级节点
group({
// 可以逐级嵌套
children: [],
}),
],
}),
consumer(() => {
// consumer 回调的返回值等价于 children
return [];
}),
],
});
});
children
支持传入 item
、group
、section
、consumer
, 以及 JSX:
tsx
defineFatForm(({ item, group, section, consumer }) => {
return () => ({
// 定义下级节点
children: [
group({
/*..*/
}),
item({
/*..*/
}),
section({
/*..*/
}),
consumer(() => {
/*..*/
}),
<div>JSX</div>,
someCondition && <div>JSX</div>,
],
});
});
为什么推荐使用 consumer
而不是直接写 JSX
如果想要实现表单联动等, 依赖于表单数据的渲染,我们推荐使用 consumer, 因为它能够实现精确渲染。下面对比两个示例(打开 Vue 开发者工具的 Highlight Updates 查看重新渲染的范围):
查看代码
tsx
import { defineFatForm } from '@wakeadmin/components';
export const UseConsumer = defineFatForm(({ item, consumer }) => {
return () => ({
children: [
item({ label: 'a', prop: 'a' }),
item({ label: 'b', prop: 'b' }),
item({ label: 'c', prop: 'c' }),
consumer(({ values }) => {
return <div>{JSON.stringify(values)}</div>;
}),
],
});
});
/**
* 未使用 consumer, 将导致全量渲染
*/
export const NotUseConsumer = defineFatForm(({ item, form }) => {
return () => ({
children: [
item({ label: 'a', prop: 'a' }),
item({ label: 'b', prop: 'b' }),
item({ label: 'c', prop: 'c' }),
<div>{JSON.stringify(form.value?.values)}</div>,
],
});
});
示例
来看一个相对复杂的案例,这个案例来源于惟客云(会员中心/积分倍率活动)。这个例子会展示 checkboxs 原件,FatFormGroup 的妙用:
查看代码
tsx
import { defineFatForm } from '@wakeadmin/components';
enum Type {
Or,
And,
None,
}
interface Values {
type: Type;
conditions: string[];
}
export default defineFatForm<Values>(({ group, renderChild, item }) => {
return () => {
return {
initialValue: {
type: Type.Or,
conditions: [],
},
layout: 'vertical',
children: [
group({
label: '触发条件',
gutter: 'sm',
vertical: true,
children: [
item({
prop: 'type',
valueType: 'radio',
valueProps: {
options: [
{
label: '满足一项选中条件即可',
value: Type.Or,
},
{
label: '满足全部选中条件',
value: Type.And,
},
{
label: '无限制',
value: Type.None,
},
],
},
// 依赖于 conditions, 即 conditions 变化时会触发它重新验证
dependencies: 'conditions',
rules: values => ({
validator(_rule, value, callback) {
if (value !== Type.None && !values.conditions?.length) {
callback(new Error('请选择至少一条触发时间条件'));
} else {
callback();
}
},
}),
}),
// group 可以控制下级的一些状态,比如 disabled
group({
disabled: f => f.values.type === Type.None,
children: [
item({
prop: 'conditions',
valueType: 'checkboxs',
valueProps: {
// 垂直布局
vertical: true,
options: [
{
label: active =>
renderChild(
group({
// 当选项未选中时禁用表单, 如果选中,传入 undefined, 让 group 从父级继承 disabled 状态
disabled: !active ? true : undefined,
children: [
'每年',
item({
prop: 'year.dateRange',
valueType: 'date-range',
required: true,
width: 'small',
}),
'时间段',
item({
prop: 'year.timeRange',
valueType: 'time-range',
required: true,
width: 'small',
}),
],
})
),
value: 'year',
},
{
label: active =>
renderChild(
group({
// 当选项未选中时禁用表单
disabled: !active ? true : undefined,
children: [
'每月',
item({
prop: 'month.date',
valueType: 'select',
valueProps: {
options: [{ value: 1, label: '1' }],
},
width: 'mini',
required: true,
}),
'时间段',
item({
prop: 'month.timeRange',
width: 'small',
valueType: 'time-range',
required: true,
}),
],
})
),
value: 'month',
},
],
},
}),
],
}),
],
}),
],
};
};
});
要点:
FatFormGroup
配置的状态可以被下级继承。这些状态包含 mode, size, disabled, hidden, hideMessageOnPreview, clearable, col 等。如果下级显式配置了这些属性,那么将以下级的优先。renderChild
用于将 item, group, consumer, section 等 helper 的输出转换为 JSX。这是一个底层方法,用于一些复杂的场景。- 当
FatFormItem
的状态为 hidden,disabled 或者 mode 为 'preview' 时,针对该字段的验证规则也会自动移除。 rules
支持传入一个函数,用于依赖上下文的一些验证。 另外一个配合 dependencies 配置依赖的其他字段,当这些字段变化时重新验证。- 最佳实践是在 FatFormItem 上配置 rule,而不是在 FatForm 上。这样更加灵活,尤其是在动态表单的场景。