Form API

Form 组件是 Schema FormX 的核心组件,用于渲染和管理表单。

导入

import { Form } from '@schema-formx/react';

属性

schema

表单字段配置,可以是字段数组或分组数组。

type Schema = FieldSchema[] | GroupSchema[];

类型FieldSchema[] | GroupSchema[]

必填:是

示例

// 字段数组
const schema: FieldSchema[] = [
  { type: 'string', name: 'name', label: '姓名' },
  { type: 'string', name: 'email', label: '邮箱' }
];

// 分组数组
const schema: GroupSchema[] = [
  {
    key: 'basic',
    props: { title: '基本信息' },
    fields: [
      { type: 'string', name: 'name', label: '姓名' }
    ]
  }
];

<Form schema={schema} />

layout

布局配置,控制表单的样式和布局。

type Layout = {
  formClassName?: string;
  formStyle?: CSSProperties;
  groupClassName?: string;
  groupStyle?: CSSProperties;
  fieldClassName?: string;
  fieldStyle?: CSSProperties;
  labelClassName?: string;
  labelStyle?: CSSProperties;
  tipClassName?: string;
  tipStyle?: CSSProperties;
  controlClassName?: string;
  controlStyle?: CSSProperties;
  groups?: Record<string, SetUI<'group'>>;
  fields?: Record<string, SetUI<'field' | 'label' | 'control' | 'tip'>>;
};

类型Layout

必填:是

示例

const layout: Layout = {
  formClassName: 'my-form',
  formStyle: { maxWidth: '600px' },
  fieldClassName: 'form-field',
  fieldStyle: { marginBottom: '16px' },
  labelClassName: 'field-label',
  labelStyle: { fontWeight: '500' }
};

<Form layout={layout} />

components

自定义组件映射,用于注册自定义表单控件和分组容器。

type Components = {
  DefaultControl: FieldComponent;    // 必填
  DefaultGroup?: GroupComponent;     // 可选
  [key: string]: FieldComponent | GroupComponent | undefined;
};

类型object

必填:是

示例

const components = {
  DefaultControl: MyInputComponent,
  CustomSelect: MySelectComponent,
  DefaultGroup: MyGroupComponent
};

<Form components={components} />

data

受控数据,用于完全控制表单数据。

type Data = Record<string, unknown>;

类型object

必填:否

示例

const [formData, setFormData] = useState({ name: '', email: '' });

<Form 
  data={formData}
  onChange={({ data }) => setFormData(data)}
/>

defaultData

默认数据,用于设置表单的初始值。

type DefaultData = Record<string, unknown>;

类型object

必填:否

示例

const defaultData = {
  name: '张三',
  email: 'zhangsan@example.com'
};

<Form defaultData={defaultData} />

onChange

数据变化回调,当表单数据发生变化时触发。

type OnChange = (params: OnChangeParams) => void;

type OnChangeParams = {
  data: Readonly<Record<string, unknown>>;
  name: string;
  value: unknown;
};

类型function

必填:否

注意事项

  • onChange 在字段值每次变化时立即触发(非失焦触发)
  • 每次值变化会同步触发依赖联动和字段验证
  • 回调中的 data 参数为冻结对象(Object.freeze),需浅拷贝后修改

示例

<Form 
  onChange={({ data, name, value }) => {
    console.log('字段变化:', name, value);
    console.log('当前数据:', data);
  }}
/>

实例方法

通过 ref 获取表单实例,可以调用以下方法:

getData

获取表单数据。返回的对象会被 Object.freeze 冻结,直接修改会抛出运行时错误。

type GetData = () => Record<string, unknown>;

注意事项

  • 返回的数据是冻结对象,不可直接修改
  • 如果需要修改数据,请先进行浅拷贝:const data = { ...formRef.current.getData() }
  • onChange 回调中的 data 参数同样被冻结

示例

const formRef = useRef<FormInstance>(null);

const handleGetData = () => {
  const data = formRef.current.getData();
  console.log('表单数据:', data);
  // ⚠️ data.name = 'new' 会抛出 TypeError
  // ✅ 正确做法:const copy = { ...data }; copy.name = 'new';
};

<Form ref={formRef} schema={schema} />

setData

设置表单数据。

type SetData = (data: Record<string, unknown>) => void;

注意事项

  • 调用 setData 会自动触发所有字段的依赖联动(onDepsChange),以确保联动字段的 schema 和数据保持一致
  • 传入的数据会完全替换现有表单数据(非浅合并)
  • setData 不会触发 onChange 回调,也不会触发验证。如需验证,请手动调用 validate()

示例

const formRef = useRef<FormInstance>(null);

const handleSetData = () => {
  formRef.current.setData({
    name: '李四',
    email: 'lisi@example.com'
  });
};

<Form ref={formRef} schema={schema} />

reset

重置表单到默认数据。

type Reset = () => void;

注意事项

  • 调用 reset 会自动触发所有字段的依赖联动(onDepsChange),以确保联动字段的 schema 和数据恢复一致
  • reset 不会触发 onChange 回调,也不会触发验证

示例

const formRef = useRef<FormInstance>(null);

const handleReset = () => {
  formRef.current.reset();
};

<Form ref={formRef} schema={schema} />

validate

验证表单,返回验证结果。

type Validate = () => Promise<{
  data: Record<string, unknown>;
  errors: Record<string, string> | null;
}>;

示例

const formRef = useRef<FormInstance>(null);

const handleSubmit = async () => {
  const { data, errors } = await formRef.current.validate();
  
  if (errors) {
    console.log('验证失败:', errors);
    return;
  }
  
  console.log('验证通过:', data);
};

<Form ref={formRef} schema={schema} />

isDirty

检查表单数据是否偏离初始值(与 defaultData 对比)。

type IsDirty = () => boolean;

等价值判断规则:比较时会根据字段类型视某些值为等价:

字段类型视为等价于 undefined/null 的值
string'', undefined
number'', null, undefined
boolean'', null, undefined
object{}, undefined
array[], undefined

例如,defaultData 中某字段值为空字符串 '',当前值为 undefined,对于 string 类型字段 isDirty() 返回 false

示例

const formRef = useRef<FormInstance>(null);

const handleCheckDirty = () => {
  const isDirty = formRef.current.isDirty();
  console.log('数据是否修改:', isDirty);
};

<Form ref={formRef} schema={schema} />

Layout 详细配置

SetUI 类型

SetUI 是一个工具类型,用于生成带前缀的 ClassName 和 Style 配置。

type SetUI<Prefix extends string> = {
  [Key in `${Prefix}${'ClassName' | 'Style'}`]?: Key extends `${Prefix}Style`
    ? CSSProperties
    : string;
};

示例

// SetUI<'form'> 等价于:
type FormUI = {
  formClassName?: string;
  formStyle?: CSSProperties;
};

Form 配置

const layout = {
  formClassName: 'my-form',
  formStyle: {
    maxWidth: '600px',
    margin: '0 auto',
    padding: '20px'
  }
};

Group 配置

const layout = {
  groupClassName: 'form-group',
  groupStyle: {
    marginBottom: '24px',
    padding: '20px',
    border: '1px solid #e0e0e0',
    borderRadius: '8px'
  }
};

Field 配置

const layout = {
  fieldClassName: 'form-field',
  fieldStyle: {
    marginBottom: '16px'
  }
};

Label 配置

const layout = {
  labelClassName: 'field-label',
  labelStyle: {
    display: 'block',
    marginBottom: '6px',
    fontWeight: '500',
    color: '#333'
  }
};

Tip 配置

const layout = {
  tipClassName: 'field-tip',
  tipStyle: {
    color: '#ff4d4f',
    fontSize: '12px',
    marginTop: '4px'
  }
};

Control 配置

const layout = {
  controlClassName: 'field-control',
  controlStyle: {
    width: '100%'
  }
};

按分组配置

const layout = {
  groups: {
    basic: {
      groupClassName: 'basic-group',
      groupStyle: { backgroundColor: '#f0f8ff' }
    },
    advanced: {
      groupClassName: 'advanced-group',
      groupStyle: { backgroundColor: '#fff8e1' }
    }
  }
};

按字段配置

const layout = {
  fields: {
    username: {
      fieldClassName: 'username-field',
      labelClassName: 'username-label',
      controlClassName: 'username-control'
    },
    email: {
      fieldClassName: 'email-field',
      labelClassName: 'email-label',
      controlClassName: 'email-control'
    }
  }
};

渲染 HTML 结构与 CSS 属性

Form 组件渲染时会自动生成以下 HTML 结构和 CSS data 属性,可用于外部样式控制:

<!-- 渲染结构示意 -->
<form class="formClassName">
  <div class="groupClassName">  <!-- Group 容器 -->
    <div class="fieldClassName">
      <label data-required="true" for="fieldName">字段名</label>
      <div class="controlClassName" data-error="true">
        <!-- 表单控件 -->
        <span class="tipClassName">错误信息</span>
      </div>
    </div>
  </div>
</form>

CSS data 属性

属性说明
data-required"true"字段设置了 required: true 时 label 上显示
data-error"true"字段存在验证错误时 control 容器上显示

使用示例

/* 必填字段标签添加红色星号 */
label[data-required]::after {
  content: ' *';
  color: red;
}

/* 有错误的控件容器添加红色边框 */
[data-error="true"] {
  border: 1px solid #ff4d4f;
  border-radius: 4px;
}

相关文档

使用示例

基础用法

import { Form } from '@schema-formx/react';
import type { FieldSchema } from '@schema-formx/react';

const schema: FieldSchema[] = [
  { type: 'string', name: 'name', label: '姓名', required: true },
  { type: 'string', name: 'email', label: '邮箱', required: true }
];

function MyForm() {
  return <Form schema={schema} />;
}

受控表单

import { useState } from 'react';
import { Form } from '@schema-formx/react';

function MyForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });

  return (
    <Form 
      schema={schema}
      data={formData}
      onChange={({ data }) => setFormData(data)}
    />
  );
}

表单验证

import { useRef } from 'react';
import { Form } from '@schema-formx/react';
import type { FormInstance } from '@schema-formx/react';

function MyForm() {
  const formRef = useRef<FormInstance>(null);

  const handleSubmit = async () => {
    const { data, errors } = await formRef.current.validate();
    if (errors) {
      alert('验证失败');
      return;
    }
    console.log('提交数据:', data);
  };

  return (
    <>
      <Form ref={formRef} schema={schema} />
      <button onClick={handleSubmit}>提交</button>
    </>
  );
}