Shared
@skyroc/form
类型安全的 React 表单 primitive,路径级精确订阅 + StandardSchema 校验
概览
| 项 | 值 |
|---|---|
| 包名 | @skyroc/form |
| 目录 | packages/primitives/filed-form |
| 版本 | 0.0.2 |
| 运行时依赖 | @radix-ui/react-slot、@skyroc/type-utils、@skyroc/utils |
| peer | react、react-dom(^16.8 – ^19) |
| 子入口 | .、./react |
| 测试 | ✅ 5 个 |
| README | ✅ README.md + README.zh.md |
@skyroc/form 是一个类型安全的 React 表单库。设计核心是把表单数据形状(Values)作为泛型贯穿所有 API,让字段路径不是 string 而是 Values 结构推导出的合法字面量联合,配合 ChangeTag bitmask 实现精确订阅、避免无关字段触发重渲染。
适用场景
- 字段路径 / 值类型必须类型安全
- 大型表单需要精确订阅(避免重渲染整张表)
- 想用 Zod / Yup / Valibot 等 StandardSchema 兼容库做校验
- 需要动态数组、ComputedField、Undo/Redo
核心设计
1. 泛型 Values 贯穿全链路
type Profile = { user: { name: string; age: number }; tags: string[] };
const form = useForm<Profile>();
form.setFieldValue('user.name', 'Alice'); // ✅ 类型安全
form.setFieldValue('user.xxx', 'Alice'); // ❌ 编译报错(user.xxx 不存在)
form.setFieldValue('user.age', 'Alice'); // ❌ 编译报错(age 不是 string)2. 路径类型由 @skyroc/type-utils 驱动
FieldProps.name: AllPathsKeys<Values>useWatch(name: T)→PathToDeepType<Values, T>List/useArrayField使用ArrayKeys<Values>/ArrayElementValue<Values, K>ValuesOptions.arrayOp(name)的 insert/remove 参数与数组元素类型绑定
3. ChangeTag bitmask 精确订阅
import { useWatch, useSelector } from '@skyroc/form';
// 只在 user.name 变化时重渲染
const name = useWatch('user.name');
// 自定义选择器 + 精确依赖
const summary = useSelector(
values => `${values.user.name} - ${values.user.age}`,
{ deps: ['user.name', 'user.age'] }
);4. StandardSchema 集成
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
age: z.number().int().min(18)
});
<Form schema={schema}>
<Field name="email"><Input /></Field>
<Field name="age"><Input type="number" /></Field>
</Form>;Schema 通过 ~standard.validate 接口接入,兼容 Zod / Yup / Valibot / ArkType 等。
主入口导出
组件
| 导出 | 作用 |
|---|---|
Form | 表单容器,提供 FormInstance context |
Field | 字段绑定(自动处理 value / onChange / onBlur) |
List | 动态数组字段(增删改、key 管理) |
ComputedField | 派生字段(基于其它字段计算) |
Hooks
| Hook | 用途 |
|---|---|
useForm<Values>() | 创建 form 实例(或从外部传入) |
| `useWatch(name | names)` |
useSelector(selector, { deps }) | 自定义选择器 + 依赖路径 |
useFieldState(name) | 字段 meta(touched / dirty / validating) |
useFieldError(names) | 错误对象(推导形状) |
useArrayField(name) | 数组字段操作(push / remove / insert / move) |
useEffectField(name, effect) | 字段变化副作用 |
useUndoRedo() | 撤销 / 重做 |
类型
FormInstance、FormProps、FieldProps、ListProps、ComputedFieldProps、ValidateErrorEntity、Rule、Meta、SubscribeMaskOptions、Action、ValidateMessages、AllPathsKeys、FieldElement 等。
子入口
| 子路径 | 内容 |
|---|---|
. | form-core 类型 + 全部 React API |
./react | 仅 React 层(组件 + hooks) |
目录结构
src/
├── index.ts
├── form-core/ # 框架无关核心
│ ├── createStore.ts # FormStore 状态引擎
│ ├── event.ts # ChangeTag bitmask 订阅
│ ├── middleware.ts # Action / Middleware 管道
│ ├── types.ts
│ ├── validate.ts
│ ├── validation.ts
│ └── resolver/
│ ├── resolver.ts # StandardSchemaV1 / 自定义 schema
│ ├── standard.ts
│ └── utils.ts
└── react/
├── components/
│ ├── Form.tsx
│ ├── Field.tsx
│ ├── List.tsx
│ └── ComputedField.tsx
└── hooks/
├── FieldContext.ts # FormInstance 实现 / 类型核心
├── useForm.ts
├── useWatch.ts
├── useSelector.ts
├── useFieldState.ts
├── useFieldError.ts
├── useFieldArray.ts
├── useFieldEffect.ts
└── useUndoRedo.ts基础示例
import { Form, Field, useForm } from '@skyroc/form';
interface Login {
email: string;
password: string;
}
const LoginForm = () => {
const form = useForm<Login>();
function onSubmit(values: Login) {
console.log(values);
}
return (
<Form form={form} onFinish={onSubmit}>
<Field name="email">
<input type="email" />
</Field>
<Field name="password">
<input type="password" />
</Field>
<button onClick={() => form.submit()}>登录</button>
</Form>
);
};动态数组
import { List, useArrayField } from '@skyroc/form';
<List name="tags">
{({ fields, add, remove }) => (
<>
{fields.map((field, i) => (
<Field key={field.key} name={`tags.${i}`}>
<input />
</Field>
))}
<button onClick={() => add('')}>+</button>
</>
)}
</List>;form-core 与 React 层
form-core/ 是框架无关的状态引擎:
FormStore:字段注册、值、meta、校验、数组操作、submit 生命周期ChangeTagbitmask:把"值/校验状态/数组结构"变化标记为不同位,订阅时按位匹配Middleware:Action 拦截器,schema resolver 也是 middleware
react/ 层把 form-core 桥接到 React:
Form创建 store 并通过 context 传递Field用useSyncExternalStore订阅特定路径- 各 hook 包装 store 提供 React 友好的 API
理论上 form-core 可以脱离 React 复用(如 React Native、Solid),但当前 ./react 是唯一公开子入口。
在 web-ui 中的使用
@skyroc/web-ui 的 Form preset 组件基于本包封装,自动获得:
- antd 风格的 label / error 视觉
- 与 Radix
Slot集成的 Control 绑定 - Tailwind variant 自动应用
import { Form, FormField } from '@skyroc/web-ui';
<Form>
<FormField name="email" label="邮箱">
<Input />
</FormField>
</Form>;中间件示例
const logger: Middleware = (action, next, store) => {
console.log('[form]', action.type, action.payload);
return next(action);
};
useForm({ middlewares: [logger] });测试
5 个测试文件覆盖:
createStore:状态引擎validation:校验逻辑hooks:React hooks 行为react-components:组件交互event-middleware-resolver:事件 + 中间件 + schema 解析
详细文档
- 中文:
packages/primitives/filed-form/README.zh.md - 英文:
packages/primitives/filed-form/README.md
两份 README 都包含完整 API 参考、进阶示例与设计动机。