自动

宣布 TanStack Form v1

作者 Corbin Crutchley 于 2025 年 3 月 3 日。 TanStack Form v1

我们很高兴地宣布 TanStack Form 的第一个稳定版本现已上线,可用于生产环境!🥳

我们为此次发布支持五种框架:React、Vue、Angular、Solid 和 Lit,以及针对每个框架的多种功能。

如何安装

shell
$ npm i @tanstack/react-form
# or
$ npm i @tanstack/vue-form
# or
$ npm i @tanstack/angular-form
# or
$ npm i @tanstack/solid-form
# or
$ npm i @tanstack/lit-form
$ npm i @tanstack/react-form
# or
$ npm i @tanstack/vue-form
# or
$ npm i @tanstack/angular-form
# or
$ npm i @tanstack/solid-form
# or
$ npm i @tanstack/lit-form

一些历史

大约在两年前,我看到 Tanner 在 BlueSky(当时是一个仅限邀请的平台)上发布了他正在开发一个新项目:TanStack Form。

A back and forth between Tanner and myself on Bluesky about TanStack Form

当时,我刚刚为 React 推出了一个名为“HouseForm”的替代表单库,并且我立即被 Tanner 的库带来的一些想法所吸引。

我很有幸参加了 Tanner 很快也要参加的一个黑客马拉松,我们得以花些时间将 HouseForm 的一些 API 集成到项目中。

从那时起,Tanner 将 Form 的大部分控制权交给了我和一群出色的其他维护者。

那么,我们在这段时间里构建了什么?

特性

长时间在“烤箱”中烘烤的一个优点是,TanStack Form 推出时就提供了大量功能,您可以立即使用。

让我们以 React 的适配器为例,介绍其中仅有的几项功能。

极致的类型安全

就像许多 TanStack 项目一样,Form 彻底改变了“类型安全”表单库的含义。

tsx
const form = useForm({
    defaultValues: {
        name: "",
        age: 0
    }
});

// TypeScript will correctly tell you that `firstName` is not a valid field
<form.Field name="firstName"/>

// TypeScript will correctly tell you that `name`'s type is a `string`, not a `number`
<form.Field name="name" children={field => <NumberInput value={field.state.value}/>}/>
const form = useForm({
    defaultValues: {
        name: "",
        age: 0
    }
});

// TypeScript will correctly tell you that `firstName` is not a valid field
<form.Field name="firstName"/>

// TypeScript will correctly tell you that `name`'s type is a `string`, not a `number`
<form.Field name="name" children={field => <NumberInput value={field.state.value}/>}/>

我们甚至支持对 <form.Field> 中返回的错误进行类型检查。

tsx
<form.Field
  name="age"
  validators={{
    onChange: ({ value }) => (value < 12 ? { tooYoung: true } : undefined),
  }}
  children={(field) => (
    <>
      <NumberInput value={field.state.value} />
      // TypeScript will correctly tell you that `errorMap.onChange` // is an object,
      not a string
      <p>{field.state.meta.errorMap.onChange}</p>
    </>
  )}
/>
<form.Field
  name="age"
  validators={{
    onChange: ({ value }) => (value < 12 ? { tooYoung: true } : undefined),
  }}
  children={(field) => (
    <>
      <NumberInput value={field.state.value} />
      // TypeScript will correctly tell you that `errorMap.onChange` // is an object,
      not a string
      <p>{field.state.meta.errorMap.onChange}</p>
    </>
  )}
/>

对了,我们还支持基于字段的验证以及表单验证。可以随意组合它们!

最棒的是什么?您无需传递任何 TypeScript 泛型即可获得此级别的类型安全性。所有内容都从您的运行时用法推断而来。

模式验证

感谢 ZodValibotArkType 的创建者们的出色工作,我们开箱即支持 Standard Schema;无需其他包。

tsx
const userSchema = z.object({
  age: z.number().gte(13, 'You must be 13 to make an account'),
})

function App() {
  const form = useForm({
    defaultValues: {
      age: 0,
    },
    validators: {
      onChange: userSchema,
    },
  })
  return (
    <div>
      <form.Field
        name="age"
        children={(field) => {
          return <>{/* ... */}</>
        }}
      />
    </div>
  )
}
const userSchema = z.object({
  age: z.number().gte(13, 'You must be 13 to make an account'),
})

function App() {
  const form = useForm({
    defaultValues: {
      age: 0,
    },
    validators: {
      onChange: userSchema,
    },
  })
  return (
    <div>
      <form.Field
        name="age"
        children={(field) => {
          return <>{/* ... */}</>
        }}
      />
    </div>
  )
}

异步验证

但这还不是全部!我们还支持使用异步函数来验证您的代码;并内置了防抖和基于 AbortSignal 的取消功能。

tsx
<form.Field
  name="age"
  asyncDebounceMs={500}
  validators={{
    onBlurAsync: async ({ value, signal }) => {
      const currentAge = await fetchCurrentAgeOnProfile({ signal })
      return value < currentAge ? 'You can only increase the age' : undefined
    },
  }}
/>
<form.Field
  name="age"
  asyncDebounceMs={500}
  validators={{
    onBlurAsync: async ({ value, signal }) => {
      const currentAge = await fetchCurrentAgeOnProfile({ signal })
      return value < currentAge ? 'You can only increase the age' : undefined
    },
  }}
/>

平台支持

我们不仅像最初提到的那样支持多种框架;我们还支持多种运行时。无论您使用的是 React Native、NativeScript,甚至是 Next.js 或 TanStack Start 等 SSR 解决方案,我们都能满足您的需求。

事实上,如果您使用 SSR 解决方案,我们甚至可以轻松实现服务器端表单验证。

typescript
// app/routes/index.tsx, but can be extracted to any other path
import { createServerValidate, getFormData } from '@tanstack/react-form/start'
import { yourSchemaHere } from '~/constants/forms'

const serverValidate = createServerValidate({
  ...formOpts,
  onServerValidate: yourSchemaHere,
})

export const getFormDataFromServer = createServerFn({ method: 'GET' }).handler(
  async () => {
    return getFormData()
  }
)
// app/routes/index.tsx, but can be extracted to any other path
import { createServerValidate, getFormData } from '@tanstack/react-form/start'
import { yourSchemaHere } from '~/constants/forms'

const serverValidate = createServerValidate({
  ...formOpts,
  onServerValidate: yourSchemaHere,
})

export const getFormDataFromServer = createServerFn({ method: 'GET' }).handler(
  async () => {
    return getFormData()
  }
)

此代码示例省略了一些相关代码以保持简洁。 有关我们 SSR 集成的更多详细信息,请查看我们的文档。

这样一来,相同的验证逻辑就可以在您的前端和后端运行。当用户浏览器禁用 JavaScript 时,您的表单甚至还会显示错误!

展望未来

然而,我们不会止步不前——我们计划在 v1 稳定后添加新功能。这些功能包括:

以及更多。

感谢

我想感谢的人太多了,一旦开始就没完没了。因此,我将分别向我想感谢的几类人致谢。

  • 感谢我们的贡献者:许多人必须齐心协力才能实现这一点。从其他 TanStack 项目的维护者给予我们的指导,到一次性的 PR;所有这些都帮助我们取得了成功。

  • 感谢我们的早期采用者:那些冒风险使用我们的人,并为我们的 API 和功能提供了宝贵的反馈。

  • 感谢报道我们工具的内容创作者:您为我们的项目带来了更多的关注——通过教育和反馈使它变得更好。

  • 感谢更广泛的社区:您使用我们工具的热情极大地激励了团队。

最后,**感谢**您花时间阅读和探索我们最新的工具。❤️