Corbin Crutchley 写于 2025年3月3日.

我们激动地宣布,TanStack Form 的第一个稳定版本现已发布,并已准备好在生产环境中使用!🥳
我们在发布时支持五个框架:React、Vue、Angular、Solid 和 Lit,以及针对每个特定框架的众多特性。
$ npm i @tanstack/react-form
# 或
$ npm i @tanstack/vue-form
# 或
$ npm i @tanstack/angular-form
# 或
$ npm i @tanstack/solid-form
# 或
$ npm i @tanstack/lit-form
$ npm i @tanstack/react-form
# 或
$ npm i @tanstack/vue-form
# 或
$ npm i @tanstack/angular-form
# 或
$ npm i @tanstack/solid-form
# 或
$ npm i @tanstack/lit-form
大约两年前,我看到了 Tanner 在 BlueSky(当时还是一个仅限邀请的平台)上发布的帖子,宣布他正在开发一个新项目:TanStack Form。

当时,我刚刚为 React 推出了一个名为“HouseForm”的替代表单库,我立刻就被 Tanner 的库所提出的一些想法迷住了。
我很幸运能参加 Tanner 不久后也要参加的一个黑客马拉松,我们得以抽出一些时间将 HouseForm 的一些 API 集成到项目中。
从那时起,Tanner 就把 Form 的大部分控制权交给了我和一群出色的其他维护者。
那么,在那段时间里我们构建了什么呢?
酝酿如此之久的一个优势是,TanStack Form 在发布第一天就推出了一系列您可以利用的特性。
让我们以 React 的适配器为例,来介绍其中的一小部分。
像许多 TanStack 项目一样,Form 彻底改变了“类型安全”表单库的含义。
const form = useForm({
defaultValues: {
name: "",
age: 0
}
});
// TypeScript 会正确地告诉你 `firstName` 不是一个有效的字段
<form.Field name="firstName"/>
// TypeScript 会正确地告诉你 `name` 的类型是 `string` 而不是 `number`
<form.Field name="name" children={field => <NumberInput value={field.state.value}/>}/>
const form = useForm({
defaultValues: {
name: "",
age: 0
}
});
// TypeScript 会正确地告诉你 `firstName` 不是一个有效的字段
<form.Field name="firstName"/>
// TypeScript 会正确地告诉你 `name` 的类型是 `string` 而不是 `number`
<form.Field name="name" children={field => <NumberInput value={field.state.value}/>}/>
我们甚至支持类型检查 <form.Field> 中返回的错误:
<form.Field
name="age"
validators={{
onChange: ({ value }) => (value < 12 ? { tooYoung: true } : undefined),
}}
children={(field) => (
<>
<NumberInput value={field.state.value} />
// TypeScript 会正确地告诉你 `errorMap.onChange` 是一个对象,
而不是一个字符串
<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 会正确地告诉你 `errorMap.onChange` 是一个对象,
而不是一个字符串
<p>{field.state.meta.errorMap.onChange}</p>
</>
)}
/>
哦,是的,我们支持基于字段的验证以及表单验证。可以混合搭配使用它们!
最棒的部分是什么?您不需要传递任何 TypeScript 泛型即可获得这种级别的类型安全。一切都从您的运行时使用中推断出来。
感谢 Zod、Valibot 和 ArkType 的创建者们的出色工作,我们开箱即用地支持 Standard Schema;不需要其他包。
const userSchema = z.object({
age: z.number().gte(13, '您必须年满 13 岁才能创建帐户'),
})
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, '您必须年满 13 岁才能创建帐户'),
})
function App() {
const form = useForm({
defaultValues: {
age: 0,
},
validators: {
onChange: userSchema,
},
})
return (
<div>
<form.Field
name="age"
children={(field) => {
return <>{/* ... */}</>
}}
/>
</div>
)
}
但这还不是全部!我们还支持使用异步函数来验证您的代码;内置防抖动和基于 AbortSignal 的取消功能:
<form.Field
name="age"
asyncDebounceMs={500}
validators={{
onBlurAsync: async ({ value, signal }) => {
const currentAge = await fetchCurrentAgeOnProfile({ signal })
return value < currentAge ? '您只能增加年龄' : undefined
},
}}
/>
<form.Field
name="age"
asyncDebounceMs={500}
validators={{
onBlurAsync: async ({ value, signal }) => {
const currentAge = await fetchCurrentAgeOnProfile({ signal })
return value < currentAge ? '您只能增��年龄' : undefined
},
}}
/>
正如我们从一开始就提到的,我们不仅支持多个框架;我们还支持多个运行时。无论您使用的是 React Native、NativeScript,还是像 Next.js 或 TanStack Start 这样的 SSR 解决方案,我们都能满足您的需求。
事实上,如果您正在使用 SSR 解决方案,我们甚至可以轻松实现服务器端表单验证:
// app/routes/index.tsx,但可以提取到任何其他路径
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,但可以提取到任何其他路径
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 和功能提供宝贵反馈的人们。
感谢报道我们工具的内容创作者:你们为我们的项目带来了更多关注——通过教育和反馈使其变得更好。
感谢更广泛的社区:你们使用我们工具的热情极大地激励了团队。
最后,感谢您花时间阅读和探索我们最新的工具。❤️