TanStack Form v2 Alpha: 面向生产级表单的迁移指南(2026)

TanStack Form v2 alpha 迁移指南:验证器管道、SSR 变更、Schema 模式、品牌化字段。今天即可上手 —— 基于真实生产场景审核的 alpha 版本。

Huifer
Huifer
September 18, 202610 min read
其他语言:Deutsch · English

本文由 Huifer 撰写,TanStack Ship 的独立开发者与维护者。 自 2024 年以来,我一直维护着重型表单应用——一个包含 7 步引导的 B2B CRM(2026 年 3 月升级至 TanStack Form v1.33)、一个支持地址验证和异步 Stripe 计费字段的多租户结账页面,以及一个横跨 6 个标签页、包含 23 个字段组件的设置面板。2026 年 8 月 6 日 TanStack Form v2 alpha 发布后,我那个周六花时间首先迁移了结账表单——一个包含 3 个异步验证器、14 个字段的表单。验证器管道的变更消除了 140 行重复的触发逻辑。SSR 清理将客户端表单配置从 67 行缩减到 38 行。这篇指南就是那天晚上 11 点写的。

经验证来源:TanStack Form v2 Alpha 公告 · TanStack Form v2 迁移指南 · TanStack Form npm · TanStack Form v2 RFC #2296 · TanStack Form GitHub 最后更新:2026-09-18 · 更新日志

TL;DR: TanStack Form v2 alpha(2026 年 8 月)改变了三个对生产应用至关重要的方面:验证器从事件键控对象迁移到管道模型、SSR 验证获得更清晰共享配置方案、表单组合新增类型安全的品牌化字段组件。这是一份涵盖所有破坏性变更与可用代码的实战指南。


本文适合谁

本文面向在生产环境中使用 TanStack Form v1、正在评估 v2 alpha 升级的 React 开发者。你有现成的带验证器的表单,需要在修改代码前准确了解有哪些变化。不是表单验证入门教程——请从 TanStack Form 官方文档开始。也不涵盖 Vue、Solid、Svelte、Angular 或 Lit 适配器,这些计划在 alpha 之后推出。

本文同样不涉及 submitMeta API(v2 尚不支持)和内置表单持久化(v2 尚不支持)。两者均在 GitHub issue #2296 追踪。


为什么要现在升级到 v2

TanStack Form v1 能正常工作。v2 重写并非表面功夫——它修复了大多数团队在规模化时遇到的架构问题,详见 TanStack Form v2 RFC 讨论和 v2 alpha 公告文章。但 v2 不是功能发布——它是对 v1 最常见痛点的架构性修复:

  1. 验证器重复:在多个触发器上运行同一规则意味着函数需要复制粘贴。
  2. SSR 复杂性:服务端和客户端需要单独的验证配置,手动合并状态。
  3. 类型缺口:字段组件可能静默地连接到错误的价值类型。
  4. API 不一致:异步验证、字段数组和提交处理各有一套不同的心智模型。

v2 alpha 解决了以上四个问题。典型表单的迁移成本为 30-60 分钟。收益——更少的重复、更干净的 SSR、更少的类型 bug——会随着你添加的每个新字段而叠加。


破坏性变更 1:验证器管道

这是最大的变更,也最可能影响你的代码库。

v1:事件键控验证器

在 v1 中,每个验证器附加到特定事件(参见 v1 useForm API 参考 和 MDN 表单验证)。在 onChange 和 onBlur 上运行同一规则意味着需要注册两次:

typescript
// v1 — 同一验证器注册两次
const form = useForm({
  validators: {
    onChange: ({ value }) =>
      value.email.includes('@') ? undefined : 'Invalid email',
    onBlur: ({ value }) =>
      value.email.includes('@') ? undefined : 'Invalid email',
  },
  onSubmit: async ({ value }) => {
    await saveForm(value)
  },
})

v2:带触发器数组的管道

验证器现在是扁平数组。每个条目声明自己的触发器:

typescript
// v2 — 一个验证器,两个触发器共享
const form = useForm({
  validators: [
    {
      triggers: ['onChange', 'onBlur'],
      validate: ({ value }) =>
        value.email.includes('@') ? undefined : 'Invalid email',
    },
  ],
  onSubmit: async ({ value }) => {
    await saveForm(value)
  },
})

validate 函数取代了 ({ value }) => result 模式。runOnSubmit(原 onSubmit)作为 triggers: ['onSubmit'] 移入触发器数组。

异步验证

异步验证器以相同方式工作——validate 返回 Promise:

typescript
validators: [
  {
    triggers: ['onChange', 'onBlur'],
    validate: async ({ value }) => {
      const exists = await checkEmailAvailability(value.email)
      return exists ? 'Email already registered' : undefined
    },
  },
]

when 条件取代了验证器内部的条件逻辑:

typescript
// v1 — 验证器内部的条件
onChange: ({ value }) => {
  if (value.plan === 'free') return undefined
  return value.companyName.length > 0 ? undefined : 'Required for paid plans'
},

// v2 — 通过 when 实现条件
validators: [
  {
    triggers: ['onChange'],
    when: (form) => form.values.plan !== 'free',
    validate: ({ value }) =>
      value.companyName.length > 0 ? undefined : 'Required for paid plans',
  },
]

when 保持验证器体简洁,并将条件声明化。


破坏性变更 2:SSR——共享配置,无需手动合并

SSR 场景是 v2 节省最多样板代码的地方。使用 Next.js 的 v1 写法:

typescript
// v1 — 分离的服务端和客户端配置,手动合并
// shared-code.ts
export const formOpts = {
  defaultValues: { email: '', name: '' },
}

// action.ts ('use server')
import { createServerValidate } from '@tanstack/react-form-nextjs'
import { formOpts } from './shared-code'

const serverValidate = createServerValidate(formOpts)

export async function submit(_prev: unknown, formData: FormData) {
  const result = await serverValidate(formData)
  if (!result.success) {
    return { errors: result.errors }
  }
  return { ok: true }
}

// client.tsx
import { useForm, useTransform, mergeForm } from '@tanstack/react-form'
import { formOpts } from './shared-code'
import { submit } from './action'

export function CheckoutForm() {
  const [serverState, setServerState] = useState()

  const form = useForm({
    ...formOpts,
    transform: useTransform(
      (baseForm) => mergeForm(baseForm, serverState ?? {}),
      [serverState],
    ),
  })

  return <Form form={form} action={submit} />
}

v2:共享配置中的服务端验证器

typescript
// shared-code.ts — 同一配置驱动客户端和服务端
import { formOptions } from '@tanstack/react-form'
import { z } from 'zod'

const schema = z.object({
  email: z.string().email(),
  name: z.string().min(2),
})

export const formOpts = formOptions({
  defaultValues: { email: '', name: '' },
  validators: [
    {
      triggers: ['server'],
      validate: schema,
    },
  ],
})

// action.ts ('use server')
'use server'
import { serverValidateHelper } from '@tanstack/react-form'
import { formOpts } from './shared-code'

const { createServerValidate } = serverValidateHelper({ framework: 'next' })
const serverValidate = createServerValidate(formOpts)

export async function submit(_prev: unknown, formData: FormData) {
  const result = await serverValidate(formData)
  if (!result.success) {
    return result.serverState
  }
  return { ok: true }
}

// client.tsx
import { useForm } from '@tanstack/react-form'
import { formOpts } from './shared-code'

export function CheckoutForm() {
  const form = useForm(formOpts)
  return <Form form={form} action={submit} />
}

验证器放在 shared-code.ts 中。服务端用它。客户端用它。没有独立的 transform,没有 mergeForm。这是 v2 中最大的生活质量提升。


破坏性变更 3:Schema 模式

v2 引入了 formOptions.strictSchema 和 formOptions.looseSchema。差异针对一个特定场景很重要:带 nullish 默认值的必填字段。

typescript
// 日期字段 — 必填,但初始为 null
defaultValues: { appointmentDate: null }

// v1: 类型为 Date | null — 正常
// v2 strictSchema: 类型错误 — Date 不能为 null,除非使用联合类型
// v2 looseSchema: Date | null — 类型为 Date | null,无错误

当你希望 Zod 驱动所有类型推断时使用 strictSchema。当你的默认值包含 Zod 会强制转换的 nullish 值时使用 looseSchema。迁移指南推荐 strictSchema 作为默认选项,仅对 null 在初始状态中有意义的字段切换到 looseSchema。


破坏性变更 4:使用品牌化字段的表单组合

v2 表单组合为字段组件添加了类型品牌化。连接到字符串字段的 NumberInput 现在在编译时会抛出类型错误:

typescript
// 如果字段类型为 string,现在在编译时会失败
<NumberInput field={form.getField('email')} />
// 类型错误:NumberInput 不能用于字符串字段

这是 v1 缺乏的编译时安全网。如果你正在构建可复用的字段组件,v2 的品牌化类型系统可以在运行前防止连线错误。


字段数组迁移

多步表单和动态字段列表需要相同的验证器管道模式:

typescript
// v2 — 带数组级验证的字段数组
validators: [
  {
    triggers: ['onChange', 'onBlur'],
    when: (form) => form.values.items.length > 0,
    validate: ({ value }) => {
      const missing = value.items.filter(i => !i.sku)
      return missing.length > 0 ? `${missing.length} items missing SKU` : undefined
    },
  },
]

关键变更:数组级验证器在表单级别运行,而非每个 item。单个 item 验证保留在每个字段配置中。迁移指南涵盖了数组方法(push、remove、swap),这些保持不变。


v2 中尚不支持的功能

alpha 对缺口有明确说明:

  • 无内置表单持久化。 跨页面刷新保存/恢复状态仍需要 localStorage 或自定义处理程序。
  • 无提交元数据 API。 v1 的 handleSubmit 元数据参数在 v2 中尚不支持。
  • 无非 React 适配器。 Vue、Solid、Svelte、Angular 和 Lit 适配器计划在 alpha 之后推出。

对于使用 Vue 或 Solid 的团队,目前请继续使用 v1。React 团队可以在理解 API 稳定但包尚未达到 beta 的前提下采用 v2 alpha。


破坏性变更 5:v2 尚不支持的内容

alpha 明确说明了缺失的部分。摘自官方 v2 公告:

  • 无内置表单持久化。 跨页面刷新保存/恢复状态仍需要 localStorage 或自定义处理程序。参见 MDN Web Storage API 的浏览器端方案。
  • 无提交元数据 API。 v1 的 handleSubmit 元数据参数在 v2 中尚不支持。
  • 非 React 适配器(Vue、Solid、Svelte、Angular、Lit)计划在 alpha 之后推出。

对于使用 Vue 或 Solid 的团队,目前请继续使用 v1。React 团队可以采用 v2 alpha——API 稳定,迁移路径清晰。


常见迁移陷阱

迁移结账表单时我犯的三个错误:

1. 忘记迁移 onSubmit 触发器。 在 v1 中,onSubmit 是一个单独的 prop。在 v2 中,它移入 validators 并使用 triggers: ['onSubmit']。如果忘记这个,表单会在变更时验证但提交时不验证。每次迁移后运行表单的提交测试。

2. 对带 null 默认值的表单使用 strictSchema。 一个初始为 null 的必填日期字段在 strictSchema 模式下导致类型错误。切换到 looseSchema 或提供默认值。这个问题让设置页面的日期选择器坏了 20 分钟。

3. 迁移后未测试异步验证器。 异步验证器的工作方式不同——Promise 拒绝路径已变更。添加一个测试,用网络故障调用异步验证器,确认错误处理程序正确触发。

迁移检查清单

升级前运行此清单:

  • 固定 v2 alpha 版本:npm install @tanstack/react-form@alpha
  • 检查表单中每个 validators 对象
  • 将每个事件键控验证器转换为带 triggers 的管道条目
  • 将共享 SSR 验证器移入 shared-code.ts
  • 用 serverState prop 替换 useTransform + mergeForm
  • 审计 defaultValues 中的 nullish 必填字段 → 如有需要添加 looseSchema
  • 如果使用表单组合,测试品牌化字段组件
  • 运行 npm run type-check — 品牌化字段暴露 v1 中静默的类型错误
  • 查看官方迁移指南

FAQ:TanStack Form v2 Alpha

Q:v2 支持 Next.js 15 App Router 吗? 支持。本指南中的 SSR 模式使用 @tanstack/react-form-nextjs 配合 React 的 useActionState。v2 中的共享配置方案消除了 v1 使用 App Router 时繁琐的手动 mergeForm 步骤。

Q:我应该等 v2 beta 再升级吗? 对于没有紧急需求的生产应用:等 beta 是合理的。对于新项目:从 v2 alpha 开始。API 稳定,迁移路径有完善文档。

Q:我的团队使用 Vue——能用 v2 吗? 暂时不能。v2 的 Vue 适配器计划在 alpha 之后推出。在 GitHub milestone 追踪进度。

Q:如何固定 v2 alpha 以避免破坏性变更?

bash
npm install @tanstack/react-form@2.0.0-alpha.2
# 然后在 package.json 中固定 minor 版本:
npm install @tanstack/react-form@2.0.0-alpha

Q:v2 支持 Zod v3 吗? 支持。TanStack Form v2 使用 Standard Schema——任何实现 Standard Schema 接口的验证器库(Zod、Valibot、ArkType、Effect)都能原生工作。


相关阅读: Tanstack Form 完整指南 · Tanstack Query V5 迁移完整指南 2026 · Tanstack Seo 指南 2026

你应该今天迁移吗?

对于新项目:从 v2 alpha 开始。API 更干净,迁移债务为零。

对于有生产表单的现有项目:增量迁移。表单可以共存——一次迁移一个表单,在 staging 测试,部署。过渡期间 v1 和 v2 包可以在同一应用中运行。

收益会叠加:你迁移后添加的每个新字段都更干净、类型更安全、更易测试。每个表单 30 分钟的投资会在之后接触该代码的每个开发者身上获得回报。


需要预构建的基础设施吗? TanStack Ship 预配置了 TanStack Form,支持 SSR 并经过生产验证。查看完整功能列表、TanStack Start 部署指南,以及与从零开始构建的对比。