本文介绍 asControl() 和 asVirtualGroup() 如何改变 Group/Intersect 类型的默认行为。
control-group-intersect-virtual.definition.ts
import { asVirtualGroup } from '@piying/view-angular';
import * as v from 'valibot';
import { actions } from '@piying/view-angular-core';
export const schema = v.object({
name: v.pipe(v.string(), actions.attributes.set({ placeholder: '姓名' })),
settings: v.pipe(
v.intersect([
v.object({
theme: v.pipe(v.string(), actions.attributes.set({ placeholder: '主题' })),
}),
v.object({
lang: v.pipe(v.string(), actions.attributes.set({ placeholder: '语言' })),
}),
]),
asVirtualGroup(),
),
});
export const model = { name: '张三', settings: { theme: 'dark', lang: 'zh-CN' } };
默认情况下,Piying-View 会将 v.object()、v.array() 等容器类型展开为子字段的集合:
control-group-default.definition.ts
import * as v from 'valibot';
export const schema = v.object({
address: v.object({
city: v.string(),
street: v.string(),
}),
});
export const model = { address: { city: '上海', street: '南京路' } };
asControl() 让 Group/Array 类型的 Schema 不展开,而是作为一个独立的控件节点:
control-group-as-control.definition.ts
import * as v from 'valibot';
import { asControl, setComponent } from '@piying/view-angular-core';
export const schema = v.object({
address: v.pipe(
v.object({
city: v.string(),
street: v.string(),
}),
// 仅测试
setComponent('textarea'),
asControl(),
),
});
export const model = { address: { city: '上海', street: '南京路' } };
此时 address 对应一个 FieldControl 而非 FieldGroup,整个对象作为单个值处理。
场景一:将整个嵌套对象传递给自定义组件
当你的自定义组件需要接收整个嵌套对象(而不是逐个字段)时,使用 asControl():
const schema = v.object({
profile: v.pipe(
v.object({
name: v.string(),
age: v.number(),
}),
asControl(), // 作为独立控件
setComponent('profile-editor'), // 自定义编辑器组件
),
});
// ProfileEditorComponent 接收整个 profile 对象
场景二:Union 类型作为单个控件
const schema = v.object({
value: v.pipe(
v.union([v.string(), v.number()]),
asControl(), // 不展开为 logic group
setComponent('value-input'), // 单一输入组件
),
});
场景三:Array 作为单个控件
const schema = v.object({
items: v.pipe(
v.array(v.string()),
asControl(), // 整个数组作为一个值
setComponent('array-editor'), // 自定义数组编辑器
),
});
默认情况下,v.intersect() 会被转换为 FieldLogicGroup。当需要在 intersect 内部保持 group 语义(而不是逻辑分组)时,使用 asVirtualGroup()。
control-group-as-virtual.definition.ts
import * as v from 'valibot';
export const schema = v.intersect([
v.object({ name: v.string() }),
v.object({ age: v.number() }),
]);
export const model = { name: '张三', age: 25 };
| 特性 |
asControl() |
asVirtualGroup() |
| 作用对象 |
Group / Array / Union |
Intersect |
| 默认行为 |
展开为子字段集合 |
FieldLogicGroup |
| 使用后行为 |
作为单个 FieldControl |
普通的 FieldGroup |
| 返回类型 |
FieldControl |
FieldGroup |
| 典型用途 |
将嵌套对象整体传给一个自定义组件 |
修改表单类型 |
v.intersect() + asVirtualGroup() 可以进行多级虚拟组,配合 tabs 组件实现标签页分组:
tabs-group.definition.ts
import { asVirtualGroup } from '@piying/view-angular';
import * as v from 'valibot';
import { setComponent } from '@piying/view-angular-core';
export const schema = v.pipe(
v.intersect([
v.pipe(
v.object({
k1: v.pipe(v.string()),
k2: v.pipe(v.string()),
}),
v.title('tab1'),
),
v.pipe(
v.object({
k3: v.pipe(v.string()),
k4: v.pipe(v.string()),
}),
v.title('tab2'),
),
]),
asVirtualGroup(),
setComponent('tabs'),
);
在保证数据结构正常的前提下,可通过 layout() 将字段移动到指定位置(priority 数字越小越靠前,keyPath 可引用 setAlias 设置的别名):
layout.definition.ts
import { asVirtualGroup } from '@piying/view-angular';
import * as v from 'valibot';
import { layout, setAlias } from '@piying/view-angular-core';
export const schema = v.pipe(
v.intersect([
v.pipe(v.object({}), setAlias('ly1')),
v.pipe(v.object({}), setAlias('ly2')),
v.object({
input1: v.pipe(
v.string(),
layout({ keyPath: ['@ly1'] }),
),
input2: v.pipe(
v.string(),
layout({ keyPath: ['@ly2'], priority: 3 }),
),
input3: v.pipe(
v.string(),
layout({ keyPath: ['@ly2'], priority: 2 }),
),
input4: v.pipe(
v.string(),
layout({ keyPath: ['@ly2'], priority: 1 }),
),
}),
]),
asVirtualGroup(),
);
import * as v from 'valibot';
import { asControl, asVirtualGroup, layout } from '@piying/view-angular-core';
const schema = v.object({
// Group 作为独立控件
profile: v.pipe(v.object({ name: v.string(), age: v.number() }), asControl(), setComponent('profile-card')),
// Intersect 子级作为虚拟 group
settings: v.pipe(v.intersect([v.object({ theme: v.picklist(['light', 'dark']) }), v.object({ lang: v.picklist(['zh', 'en']) })]), asVirtualGroup()),
// 嵌套使用:Intersect + Layout + asVirtualGroup
content: v.pipe(v.intersect([v.pipe(v.object({}), layout({ priority: 1 })), v.pipe(v.object({}), layout({ priority: 2 }))]), asVirtualGroup()),
});