Field API
Field API defines the configuration options for form fields, including field types, validation rules, and dependency linkage.
FieldSchema Type
type FieldSchema = {
type?: FieldType;
name: string;
label: string;
required?: boolean;
component?: string;
props?: Record<string, unknown>;
hidden?: boolean;
deps?: string[];
onDepsChange?: OnDepsChange;
validator?: Validator;
};
Property Details
type
Field type, determines which default component to use.
type FieldType = 'string' | 'number' | 'boolean' | 'object' | 'array';
Type: FieldType
Required: No
Default: 'string'
name
Field name, used for data binding.
Type: string
Required: Yes
label
Field display name.
Type: string
Required: Yes
required
Whether the field is required.
Type: boolean
Required: No
Default: false
Example:
{
name: 'username',
label: 'Username',
required: true
}
Empty value rules: When required is true, the following values are judged as empty:
Note: The required validation error message is hardcoded in Chinese as ${label}不能为空 (e.g., "Username cannot be empty"). For English error messages, use a custom validator function to override the default required validation.
component
Custom component name.
Type: string
Required: No
Component Lookup Order:
// Component lookup priority: component → DefaultControl → error
if (component) {
// 1. First lookup: custom registered component
Component = components[component];
if (!Component) {
console.error(`Component "${component}" not found`);
Component = components['DefaultControl']; // 2. Fallback: DefaultControl
}
} else {
// No component specified, use DefaultControl
Component = components['DefaultControl'];
}
props
Component props, passed to the field component.
Type: Record<string, unknown>
Required: No
hidden
Whether the field is hidden. Setting hidden: true only hides the field visually (display: none); the field is still rendered and remains in the form data. Hidden fields still participate in validation — if a hidden field has required: true, it will still trigger validation errors. Use conditional required or a custom validator to skip validation for hidden fields.
Type: boolean
Required: No
deps
Array of dependent field names.
Type: string[]
Required: No
onDepsChange
Dependency change callback, triggered when a dependent field's value changes. Supports both synchronous and asynchronous functions.
type OnDepsChange = (params: OnDepsChangeParams) => OnDepsChangeReturn | Promise<OnDepsChangeReturn>;
Parameters:
Return Value:
Notes:
- Maximum chain trigger depth is 20 times (to prevent infinite loops)
deps and onDepsChange properties cannot be modified within the callback
data parameter is read-only, do not modify directly
Example:
{
name: 'city',
label: 'City',
deps: ['province'],
onDepsChange: async ({ deps, schema }) => {
const province = deps[0];
if (!province) {
return { schema: { ...schema, hidden: true } };
}
const cities = await getCitiesByProvince(province);
return {
patch: { city: '' },
schema: { ...schema, hidden: false, props: { options: cities } }
};
}
}
validator
Custom validation function for field validation. Supports both synchronous and asynchronous validation.
type Validator = (params: ValidatorParams) => ValidatorReturn | Promise<ValidatorReturn>;
Parameters:
Return Value:
undefined: Validation passed
string: Validation failed, returns error message
Exception Handling: Exceptions thrown by the validator are automatically caught and converted to error messages:
{
name: 'field',
label: 'Field',
validator: ({ value }) => {
if (value === 'invalid') {
throw new Error('Invalid value');
}
return undefined;
}
}
// Exception converts to: errors: { field: 'Invalid value' }
Async Validation Example:
{
name: 'username',
label: 'Username',
validator: async ({ value, label }) => {
if (!value) return `${label} is required`;
const exists = await checkUsernameExists(value);
if (exists) return `${label} is already taken`;
return undefined;
}
}
Type Definitions
FieldType
type FieldType = 'string' | 'number' | 'boolean' | 'object' | 'array';
ValidatorParams
type ValidatorParams = {
value: unknown;
label: string;
data: Readonly<Record<string, unknown>>;
};
ValidatorReturn
type ValidatorReturn = undefined | string;
OnDepsChangeParams
type OnDepsChangeParams = {
deps: Array<any>;
schema: FieldSchema;
name: string;
value: unknown;
data: Readonly<Record<string, unknown>>;
isInitialTrigger: boolean;
trigger: string;
triggerCount: number;
totalTriggerCount: number;
};
OnDepsChangeReturn
type OnDepsChangeReturn = Partial<{
patch: Record<string, unknown>;
schema: FieldSchema;
}>;
Return Value Behavior:
patch: Partially merge into form data using cloneDeep
schema: Completely replace the field's schema configuration
## Usage Examples
### Basic Fields
```tsx
const schema: FieldSchema[] = [
{ type: 'string', name: 'username', label: 'Username', required: true },
{ type: 'number', name: 'age', label: 'Age' },
{ type: 'boolean', name: 'agree', label: 'Agree to Terms' }
];
Custom Component
const schema: FieldSchema[] = [
{
type: 'string',
name: 'color',
label: 'Color',
component: 'ColorPicker',
props: { colors: ['#ff0000', '#00ff00', '#0000ff'] }
}
];
Validation
const schema: FieldSchema[] = [
{
type: 'string',
name: 'email',
label: 'Email',
required: true,
validator: ({ value, label }) => {
if (!value) return `${label} is required`;
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return `${label} format is incorrect`;
return undefined;
}
}
];
Dependency Linkage
const schema: FieldSchema[] = [
{
type: 'string',
name: 'province',
label: 'Province',
props: { options: ['Guangdong', 'Zhejiang', 'Jiangsu'] }
},
{
type: 'string',
name: 'city',
label: 'City',
deps: ['province'],
onDepsChange: async ({ deps, schema }) => {
const cities = await getCitiesByProvince(deps[0]);
return {
schema: {
...schema,
props: { options: cities }
}
};
}
}
];