作者:Corbin Crutchley,发布于 2025 年 3 月 3 日。
我们很高兴地宣布 TanStack Form 的首个稳定版本已发布,可以用于生产环境了!🥳
我们在发布时支持五个框架:React、Vue、Angular、Solid 和 Lit,以及每个特定框架的众多功能。
$ 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。
当时,我刚刚发布了一个名为“HouseForm”的 React 替代表单库,并且立即被 Tanner 的库带来的一些想法所吸引。
我很幸运地参加了一个 Tanner 之后也要参加的黑客马拉松,我们得以抽出一些时间来研究将 HouseForm 中的一些 API 集成到该项目中。
从那时起,Tanner 将 Form 的大部分管理权移交给了我和一个很棒的额外维护者团队。
那么,在那段时间里我们构建了什么呢?
长期酝酿的好处之一是,TanStack Form 发布时就带有一系列您可以立即利用的功能。
让我们以 React 的适配器为例,介绍其中的一小部分功能。
像许多 TanStack 项目一样,Form 彻底改变了“类型安全”表单库的含义。
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> 中返回的错误
<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 泛型即可获得这种级别的类型安全。一切都从您的运行时使用情况中推断出来。
感谢 Zod、Valibot 和 ArkType 的创建者们的出色工作,我们开箱即用地支持 Standard Schema;无需其他软件包。
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 的取消功能
<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 解决方案,我们甚至可以使服务器端表单验证变得轻而易举
// 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 和功能提供了宝贵反馈的人们。
感谢报道我们工具的内容创作者们: 你们为我们的项目带来了更多关注,并通过教育和反馈使其变得更好。
感谢更广泛的社区: 你们对使用我们工具的热情极大地鼓舞了团队。
最后,感谢您花时间阅读和探索我们的最新工具。 ❤️