TanStack Solid Form 演进指南:从 1.21 到 1.33 的核心能力、性能优化与迁移要点
【免费下载链接】form🤖 Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, and Lit.项目地址: https://gitcode.com/GitHub_Trending/form/form
TanStack Form 为 TS/JS、React、Vue、Angular、Solid、Lit 提供 Headless、类型安全且高性能的表单状态管理。本文聚焦@tanstack/solid-form适配器在 1.21.1 至 1.33.5 版本区间内的关键变更,围绕 SSR 安全的formId、useSelector迁移、FormGroup/withFieldGroup 组合式 API、数组模式渲染优化、错误扁平化与响应式 props 修复等主题展开。读完本文,你将掌握这些 API 的演进动机、底层实现原理,以及升级到最新版本时应关注的迁移点。
版本脉络总览
@tanstack/solid-form的每一次发布都同时联动底层@tanstack/form-core,其 CHANGELOG(packages/solid-form/CHANGELOG.md)以 Changesets 的语义化格式记录了每一版本的 Patch/Minor 变更。整个 1.21.1 → 1.33.5 演进可以归纳为四条主线:
- 框架适配层补齐:新增 FormGroup API、
withFieldGroup组合 API,让 Solid 适配器与 React/Preact/Vue 对齐; - SSR 与响应式正确性:用 Solid 的
createUniqueId生成默认formId,修复withForm/withFieldGroupprops 非响应式、字段双重渲染等问题; - 渲染性能优化:数组模式下避免整组 re-render、按需订阅 meta 状态;
- API 现代化:
useStore弃用、useSelector成为推荐选择器,错误结构统一扁平化。
下面逐条深入。
SSR 安全的默认 formId
问题背景
在 1.33.4 之前,如果用户没有显式配置formId,createForm不会提供回退值,于是FormApi内部会生成一个随机 UUID。随机 UUID 在服务端渲染(SSR)与客户端渲染之间必然不同:
// 服务端渲染生成一个 id,客户端水合时又生成另一个 <form id={form.formId} />在 SolidStart 这类 SSR 框架下,把该 id 绑定到<form>上就会产生hydration mismatch(水合不匹配)。
解决方案
1.33.4 在 createForm.tsx 中改用 Solid 的createUniqueId()作为回退:
const fallbackFormId = createUniqueId() const api = new FormApi({ ...options, formId: options?.formId ?? fallbackFormId, })createUniqueId是 Solid 内置的 SSR 安全 ID:客户端环境下产出形如cl-<n>的序列,水合时则从水合上下文推导 ID,保证服务端与客户端两次渲染得到一致结果。源码注释明确说明这一改动是对 React、Preact、Vue 适配器useFormId行为的对齐。
行为验证
测试文件 createFormId.test.tsx 用两条用例锁定了该行为:
it('uses the provided formId when one is given', () => { const form = createForm(() => ({ defaultValues: { firstName: '' }, formId: 'my-form', })) // 显式 formId 被原样透传 }) it('derives the default formId from Solid so it survives hydration', () => { // 默认 formId 匹配 /^cl-\d+$/,来自 createUniqueId 序列 })迁移要点:显式传入的formId行为完全不变;只有依赖默认 id 的应用需要意识到 id 从“随机 UUID”变成了cl-<n>格式,但正是这一变化消除了 SolidStart 下的水合报错。
状态订阅 API:useStore 弃用与 useSelector
变更内容
1.33.1 开始,@tanstack/solid-form从@tanstack/solid-store重新导出useSelector,并在form实例上新增form.useSelector,同时将form.useStore标记为弃用。入口文件 index.tsx 与createForm中的实现相互印证:
extendedApi.useSelector = (selector) => useSelector(api.store, selector) /** @deprecated Use `form.useSelector` instead. */ extendedApi.useStore = extendedApi.useSelector在 createForm.tsx 的类型定义中,useStore上带有@deprecatedJSDoc 注释,IDE 会在使用旧 API 时给出提示。
推荐用法
const isSubmitting = form.useSelector((state) => state.isSubmitting) // 或直接使用从 @tanstack/solid-store 导出的 useSelector import { useSelector } from '@tanstack/solid-store'form.useSelector返回的是一个 Solid accessor(() => TSelected),符合 Solid 细粒度响应式习惯:选择器只订阅它读取的状态片段,状态变化时仅触发依赖该 accessor 的原子更新,而不会重跑整个组件。
FormGroup 与组合式 API 的落地
FormGroup API(1.33.0)
1.33.0(PR #2128)为 Solid 适配器带来了 FormGroup API,对应源码 createFormGroup.tsx。createFormGroup接收一个返回FormGroupApiOptions的 accessor:
const group = createFormGroup(() => ({ form, name: 'address', defaultValues: { street: '', city: '' }, }))其内部通过createSignal+createComputed构造makeFormGroupReactive,把FormGroupApi包装成响应式 accessor:
const [group, setGroup] = createSignal(formGroupApi, { equals: false }) const store = useSelector(formGroupApi.store, (store) => store) createComputed(() => { store() // 用 store 建立依赖追踪 setGroup(formGroupApi) })配合form.FormGroup组件(createFormGroup.tsx),可以直接在 JSX 中声明式管理一组字段。此外 FormGroup 组件还支持任意ExtendedApi,允许把自定义方法与 API 合并进 render prop。
withFieldGroup 组合 API(1.26.0)
1.26.0(PR #1783)在 Solid Form Composition 体系中新增withFieldGroup,实现在 createFormHook.tsx。createFormHook返回三个构件:
useAppForm:绑定自定义字段/表单组件的表单工厂;withForm:把render函数包装成接收{ form, ...props }的组件;withFieldGroup:把render函数包装成接收{ group, ...props }的字段组组件。
组合 API 的使用模式(与 React/Preact 适配器保持一致):
const { useAppForm, withForm, withFieldGroup } = createFormHook({ fieldComponents: { TextField, SelectField }, formComponents: { FormLayout, SubmitButton }, fieldContext, formContext, }) // 在组件中: const form = useAppForm(() => ({ defaultValues: { name: '', email: '' }, })) // 或在 render prop 中: withForm({ props: { title: 'Profile' }, render: ({ form, title }) => <form.Field name="name" />, })withForm/withFieldGroup 的响应式修复(1.28.4)
1.28.4(PR #2058)修复了一个隐蔽的 Solid 响应式 bug:此前withForm与withFieldGroup内部使用对象展开({ ...props, ...innerProps })组合 props,会提前求值 Solid 响应式 getter,把信号追踪拍平成静态快照,导致 props 变化时组件不更新。修复方式是:
- 用
mergeProps()保留 getter 描述符,维持惰性求值; - 用
createComponent()保持正确的响应式上下文。
这一修复在 createFormHook.tsx 与withFieldGroup的Render包装中都有体现(mergeProps(props ?? {}, innerProps))。
数组模式与渲染性能优化
避免数组整组重渲染(1.32.0)
1.32.0 包含两个针对数组表单的渲染修复(PR #2169、#2172):
- 数组模式下不再因为任意子项变化而触发整个数组 re-render;
- 当数组长度不变但内部值变化时,仍能正确触发重渲染(此前可能出现漏更新)。
对应实现位于 createField.tsx 的makeFieldReactive:数组模式(mode === 'array')下只订阅state.meta._arrayVersion作为值维度依赖,而把 meta 拆成isTouched、isBlurred、isDirty、errorMap、errorSourceMap、isValidating等细粒度选择器分别订阅:
const reactiveStateValue = useSelector(fieldApi.store, (state) => mode === 'array' ? state.meta._arrayVersion || 0 : state.value, ) const reactiveMetaIsTouched = useSelector( fieldApi.store, (state) => state.meta.isTouched, ) // ... isBlurred / isDirty / errorMap / errorSourceMap / isValidatingcreateComputed中统一读取这些依赖,任何一项变化时仅更新当前字段 accessor,实现“按需订阅、精准更新”。mode选项定义于 types.ts:
export interface FieldOptionsMode { mode?: 'value' | 'array' }渲染稳定性相关修复
- 1.27.7(PR #1959):修复 Solid 字段的双重渲染问题;
- 1.27.4(PR #1934):在
Form.AppField的 render 函数中使用任意 Signal,不再导致整个组件在信号变化时整体重跑,这与上面的细粒度订阅机制相互配合; - 1.28.4(PR #2035):内部实现重构以获得明显更快的性能;
- 1.28.3(PR #2041):修复 form arrays 回归,使其重新正常工作。
错误结构统一扁平化
1.28.0(PR #2003)修复了“字段挂载前手动调用form.validate()时错误结构不一致”的问题。此前会出现field.errors被错误地嵌套成[[error]]而不是[error];现在默认应用flat(1),保证无论校验发生在字段挂载前还是挂载后,错误数组结构始终一致。
// 修复前:字段挂载前校验可能得到 [[error]] // 修复后:统一为 [error] field.errors // => [error]该行为由disableErrorFlat选项控制——只有显式将其设为true才会关闭默认的扁平化。这一点对动态字段(如按条件渲染后立即读取field.errors)尤为关键。
其他值得关注的变更
- 1.28.2(PR #2038):将
@tanstack/store依赖升级到 0.8.0; - 1.29.2:移除误用的
Field.Field与useForm().useField()写法; - 1.23.8(PR #1758):优化 form-core 事件客户端(EventClient)的派发,并做小幅布局调整;
- form-core 联动:
dontValidate选项在数组修改器中生效(1.23.7)、deleteField运行时错误预防(1.23.6)等能力随@tanstack/form-core同步进入 Solid 适配器。
升级与验证建议
以当前仓库的 monorepo 结构(pnpm workspace,见 pnpm-workspace.yaml)为例,升级/验证流程为:
pnpm install # 安装依赖 pnpm --filter @tanstack/solid-form test # 运行 solid-form 测试套件测试目录 packages/solid-form/tests 提供了覆盖以上变更的测试资产:createFormId.test.tsx验证默认/自定义 formId,createForm.test.tsx、createField.test.tsx、createFormGroup.test.tsx、createFormHook.test.tsx分别覆盖各 API 的行为,.test-d.tsx文件则用于 TypeScript 类型层面的回归。
升级检查清单:
- 若使用 SolidStart,确认默认
formId从 UUID 变为cl-<n>不影响任何外部存储的 id 依赖; - 将
form.useStore(...)替换为form.useSelector(...)(旧 API 仍可用,但已弃用); - 数组字段升级到 1.32.0+ 后,验证子项值变化能正常触发对应行重渲染;
- 若你依赖旧的
[[error]]嵌套结构,评估是否启用disableErrorFlat; - 若自建了基于
withForm/withFieldGroup的组合组件,升级到 1.28.4+ 后确认 props 响应式恢复正确。
参考资源
- 版本变更记录:packages/solid-form/CHANGELOG.md
- 核心实现:createForm.tsx、createField.tsx、createFormGroup.tsx、createFieldGroup.tsx、createFormHook.tsx
- 类型定义:types.ts
- 入口导出:index.tsx
- 测试用例:createFormId.test.tsx 及其余 tests 目录下用例
- 底层引擎:
@tanstack/form-core(packages/form-core/src)
【免费下载链接】form🤖 Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, and Lit.项目地址: https://gitcode.com/GitHub_Trending/form/form
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考