Field API

Field API 定义了表单字段的配置项,包括字段类型、验证规则、依赖联动等。

FieldSchema 类型

字段配置类型,定义单个表单字段的配置。

type FieldSchema = {
  type?: FieldType;
  name: string;
  label: string;
  required?: boolean;
  component?: string;
  props?: Record<string, unknown>;
  hidden?: boolean;
  deps?: string[];
  onDepsChange?: OnDepsChange;
  validator?: Validator;
};

属性详解

type

字段类型,决定使用哪个默认组件。

type FieldType = 'string' | 'number' | 'boolean' | 'object' | 'array';

类型FieldType

必填:否

默认值'string'

示例

// 字符串类型
{ type: 'string', name: 'username', label: '用户名' }

// 数字类型
{ type: 'number', name: 'age', label: '年龄' }

// 布尔类型
{ type: 'boolean', name: 'agree', label: '同意协议' }

类型与默认组件映射

类型默认组件值类型
stringInputstring
numberInputNumbernumber
booleanCheckboxboolean
object自定义object
array自定义array

name

字段名,用于数据绑定和表单操作。

类型string

必填:是

示例

{ name: 'username', label: '用户名' }
// 表单数据:{ username: '值' }

label

字段显示名称,用于标签显示和验证错误信息。

类型string

必填:是

示例

{ name: 'username', label: '用户名' }
// 显示:用户名

required

是否必填,用于必填验证。

类型boolean

必填:否

默认值false

示例

{ 
  name: 'username', 
  label: '用户名', 
  required: true  // 必填
}

验证规则

requiredtrue 时,以下值会被判定为空:

  • null
  • undefined
  • 空字符串 ''
  • 空数组 []
  • 空对象 {}

component

自定义组件名称,用于指定字段使用的组件。

类型string

必填:否

默认值'DefaultControl'

示例

// 使用自定义组件
{
  name: 'color',
  label: '颜色',
  component: 'ColorPicker'  // 使用注册的 ColorPicker 组件
}

组件查找顺序

  1. 如果指定了 component,从 components 对象中查找对应名称的组件
  2. 如果未指定 component,默认使用 'DefaultControl'
  3. components 对象中查找 'DefaultControl' 组件
  4. 如果都未找到,显示错误提示 "error component"

组件注册示例

const components = {
  DefaultControl: MyInput,      // 默认组件
  ColorPicker: ColorPicker,     // 自定义颜色选择器
  Select: MySelect              // 自定义下拉选择器
};

<Form components={components} schema={schema} />

props

组件的额外属性,会传递给表单组件。

类型Record<string, unknown>

必填:否

示例

{
  name: 'status',
  label: '状态',
  component: 'CustomSelect',
  props: {
    options: ['启用', '禁用'],
    placeholder: '请选择状态'
  }
}

hidden

是否隐藏字段。设置 hidden: true 仅在视觉上隐藏字段(display: none),字段仍然会被渲染并存在于表单数据中。隐藏字段仍会参与验证 —— 如果隐藏字段设置了 required: true,仍会触发验证错误。如需跳过隐藏字段的验证,请使用条件 required 或自定义 validator

类型boolean

必填:否

默认值false

示例

{
  name: 'internalId',
  label: '内部ID',
  hidden: true  // 隐藏字段
}

deps

依赖字段数组,声明当前字段依赖的其他字段。

类型string[]

必填:否

示例

{
  name: 'city',
  label: '城市',
  deps: ['province']  // 依赖省份字段
}

onDepsChange

依赖变化回调,当依赖字段的值发生变化时触发。支持同步和异步函数。

类型OnDepsChange

必填:否

参数

参数类型说明
depsArray<any>依赖字段的当前值数组,顺序与 deps 声明一致
schemaFieldSchema当前字段的配置(深拷贝)
namestring当前字段名
valueunknown当前字段值
dataReadonly<Record<string, unknown>>最新的表单数据(只读)
isInitialTriggerboolean是否为表单初始化时的触发
triggerstring触发该回调的字段名
triggerCountnumber当前字段由 trigger 触发的次数
totalTriggerCountnumber当前字段被所有依赖字段触发的总次数

返回值

属性类型说明
patchRecord<string, unknown>表单数据补丁,会 merge 到原有数据
schemaFieldSchema更新后的字段配置

返回值说明

  • patch:仅返回需要更新的字段,会与原有数据合并

    // 原有数据: { name: '张三', age: 20 }
    // 返回 patch: { age: 21 }
    // 合并后: { name: '张三', age: 21 }
  • schema:返回更新后的字段配置,会替换原有配置

    // 可以更新 props、hidden、required 等属性
    // 注意:deps 和 onDepsChange 属性不能被修改

注意事项

  • 链式联动最多触发 20 次(防止无限循环)
  • depsonDepsChange 属性不能在回调中被修改
  • data 参数为只读,不要直接修改

示例

{
  name: 'city',
  label: '城市',
  deps: ['province'],
  onDepsChange: async ({ deps, schema, data }) => {
    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 }
      }
    };
  }
}

链式联动

patch 中的字段也是其他字段的依赖时,会触发链式联动:

// 省份变化 → 城市变化 → 区县变化
const schema = [
  { name: 'province', label: '省份' },
  {
    name: 'city',
    label: '城市',
    deps: ['province'],
    onDepsChange: async ({ deps, schema }) => {
      const cities = await getCities(deps[0]);
      return { patch: { city: '' }, schema: { ...schema, props: { options: cities } } };
    }
  },
  {
    name: 'district',
    label: '区县',
    deps: ['city'],
    onDepsChange: async ({ deps, schema }) => {
      const districts = await getDistricts(deps[0]);
      return { schema: { ...schema, props: { options: districts } } };
    }
  }
];

注意事项

  • 最多支持 20 次链式触发(防止死循环)
  • depsonDepsChange 属性本身不能被修改
  • data 参数是只读的,不能直接修改

validator

验证函数,用于自定义验证逻辑。支持同步和异步验证。

类型Validator

必填:否

参数

参数类型说明
valueunknown当前字段值
labelstring字段显示名称
dataReadonly<Record<string, unknown>>整个表单数据

返回值

  • undefined:验证通过
  • string:验证失败,返回错误信息

示例

// 同步验证
{
  name: 'email',
  label: '邮箱',
  validator: ({ value, label }) => {
    if (!value) return `${label}不能为空`;
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
      return `${label}格式不正确`;
    }
    return undefined;  // 验证通过
  }
}

异步验证示例

// 异步验证(如检查用户名是否已存在)
{
  name: 'username',
  label: '用户名',
  validator: async ({ value, label }) => {
    if (!value) return `${label}不能为空`;
    
    // 模拟 API 调用
    const exists = await checkUsernameExists(value);
    if (exists) {
      return `${label}已被占用`;
    }
    return undefined;
  }
}

异常处理

验证器抛出的异常会被捕获并转换为错误信息:

{
  name: 'field',
  label: '字段',
  validator: ({ value }) => {
    // 抛出的异常会被捕获
    if (value === 'invalid') {
      throw new Error('Invalid value');
    }
    return undefined;
  }
}
{
  name: 'email',
  label: '邮箱',
  validator: ({ value, label }) => {
    if (!value) return `${label}不能为空`;
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
      return `${label}格式不正确`;
    }
    return undefined;  // 验证通过
  }
}

类型定义

FieldType

字段类型枚举。

type FieldType = 'string' | 'number' | 'boolean' | 'object' | 'array';

FieldComponent

表单控件组件类型。

type FieldComponent = (props: {
  [key: string]: unknown;
  onChange?: (value: unknown) => void;
}) => ReactElement;

Validator

验证器类型。

type Validator = (params: ValidatorParams) => ValidatorReturn | Promise<ValidatorReturn>;

ValidatorParams

验证器参数类型。

type ValidatorParams = {
  value: unknown;
  label: string;
  data: Readonly<Record<string, unknown>>;
};

ValidatorReturn

验证器返回值类型。

type ValidatorReturn = undefined | string;

OnDepsChange

依赖变化回调类型。

type OnDepsChange = (params: OnDepsChangeParams) => OnDepsChangeReturn | Promise<OnDepsChangeReturn>;

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;
}>;

使用示例

基础字段

const schema: FieldSchema[] = [
  {
    type: 'string',
    name: 'username',
    label: '用户名',
    required: true,
    props: {
      placeholder: '请输入用户名'
    }
  },
  {
    type: 'number',
    name: 'age',
    label: '年龄',
    props: {
      min: 0,
      max: 120
    }
  },
  {
    type: 'boolean',
    name: 'agree',
    label: '同意协议'
  }
];

自定义组件

const schema: FieldSchema[] = [
  {
    type: 'string',
    name: 'color',
    label: '颜色',
    component: 'ColorPicker',
    props: {
      colors: ['#ff0000', '#00ff00', '#0000ff']
    }
  }
];

验证规则

const schema: FieldSchema[] = [
  {
    type: 'string',
    name: 'email',
    label: '邮箱',
    required: true,
    validator: ({ value, label }) => {
      if (!value) return `${label}不能为空`;
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
        return `${label}格式不正确`;
      }
      return undefined;
    }
  }
];

依赖联动

const schema: FieldSchema[] = [
  {
    type: 'string',
    name: 'province',
    label: '省份',
    props: { options: ['广东', '浙江', '江苏'] }
  },
  {
    type: 'string',
    name: 'city',
    label: '城市',
    deps: ['province'],
    onDepsChange: async ({ deps, schema }) => {
      const cities = await getCitiesByProvince(deps[0]);
      return {
        schema: {
          ...schema,
          props: { options: cities }
        }
      };
    }
  }
];

条件隐藏

const schema: FieldSchema[] = [
  {
    type: 'string',
    name: 'userType',
    label: '用户类型',
    props: { options: ['个人', '企业'] }
  },
  {
    type: 'string',
    name: 'company',
    label: '公司名称',
    deps: ['userType'],
    onDepsChange: ({ deps, schema }) => ({
      schema: {
        ...schema,
        hidden: deps[0] !== '企业'
      }
    })
  }
];

相关文档