
TanStack Form Svelte 实战从零构建一个带校验、异步验证与条件字段的简单表单【免费下载链接】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本篇文章以仓库中 examples/svelte/simple 这个官方最小示例为主体完整讲解如何在 Svelte搭配 Vite 与 TypeScript中接入 TanStack Form实现一个包含文本输入、复选框、条件字段、同步/异步校验、错误提示、提交状态与重置功能的真实表单。读完本文你将掌握createForm、form.Field、form.Subscribe的核心用法并理解它们背后在tanstack/svelte-form与tanstack/form-core中的底层实现。一、示例概览README 告诉你的第一件事仓库中的 simple 示例 README 内容极简只有两行命令却是整个示例的入口npm install npm run dev这两条命令分别完成依赖安装与本地开发服务器启动。从 package.json 可以看到这是一个基于Vite 7 Svelte 5 TypeScript 5.9的纯前端工程唯一的运行时依赖是tanstack/svelte-form^1.23.0这正是 TanStack Form 为 Svelte 提供的官方适配包其他依赖均为构建与编译工具链{ name: tanstack/form-example-svelte-simple, private: true, type: module, scripts: { dev: vite, build: vite build, preview: vite preview }, dependencies: { tanstack/svelte-form: ^1.23.0 }, devDependencies: { sveltejs/vite-plugin-svelte: ^5.1.1, tsconfig/svelte: ^5.0.5, svelte: ^5.39.4, typescript: 5.9.3, vite: ^7.2.2 } }示例的完整目录结构如下examples/svelte/simple/ ├── index.html # HTML 入口挂载点 #app ├── package.json ├── svelte.config.js # vitePreprocess 预处理配置 ├── tsconfig.json # 继承 tsconfig/svelte ├── vite.config.ts # Vite svelte() 插件 └── src/ ├── App.svelte # 表单主组件本文核心 ├── FieldInfo.svelte # 字段错误信息展示组件 ├── main.ts # Svelte 挂载入口 └── vite-env.d.ts从工程结构可以看出这个示例是一个零路由、零样式框架的最小化演示所有表单逻辑都集中在 src/App.svelte 中非常适合作为理解 TanStack Form 在 Svelte 中工作方式的第一课。二、项目脚手架Svelte 5 的 Vite 工程如何启动在深入表单代码之前先了解这个示例的启动链路便于你本地复现。src/main.ts 使用 Svelte 5 新的mountAPI 将App组件挂载到index.html的#app节点上import { mount } from svelte import App from ./App.svelte const app mount(App, { target: document.getElementById(app)!, }) export default appvite.config.ts 仅注册了 Svelte 插件svelte.config.js 启用了vitePreprocess以支持在.svelte文件中直接写 TypeScript。tsconfig.json继承了tsconfig/svelte的基础配置并将src下的.ts、.js、.svelte文件纳入类型检查范围。提示运行npm run build可执行生产构建npm run preview可本地预览构建产物这三个脚本都定义在 package.json 中。三、createForm声明表单的默认值与提交逻辑一切从createForm开始。src/App.svelte 的script块中创建了表单实例const form createForm(() ({ defaultValues: { firstName: , lastName: , employed: false, jobTitle: , }, onSubmit: async ({ value }) { // Do something with form data alert(JSON.stringify(value)) }, }))这里有三个关键设计理解它们比记住 API 更重要惰性初始化lazy initializercreateForm接收的是一个返回配置对象的函数而非普通对象。这正是 Svelte 5 响应式runes体系的要求——配置函数内部引用的信号变化会被$effect.pre追踪从而在每次组件更新时同步最新的表单选项。从 createForm.svelte.ts 的源码可以看到createForm内部直接new FormApi(options)实例化框架无关的核心 API随后把Field、FormGroup、Subscribe、useSelector等 Svelte 专属能力挂载到扩展 API 上const api new FormApiTFormData, ...(options) const extendedApi: typeof api SvelteFormApi... api as never extendedApi.Field (internal, props) Field(internal, { ...props, form: api as never } as never) extendedApi.Subscribe (internal, props) Subscribe(internal, { ...props, store: api.store }) onMount(api.mount) // 类似 useRef每次更新时同步最新选项不产生副作用 $effect.pre(() api.update(opts?.()))组件挂载时调用api.mount()完成表单生命周期初始化而$effect.pre(() api.update(opts?.()))则保证每次渲染前选项都被刷新——这就是配置函数模式存在的意义。defaultValues表单数据的初始值这里声明了firstName、lastName两个字符串employed布尔值以及jobTitle字符串。createForm的泛型会基于defaultValues推断出整个表单的数据类型后续所有form.Field的name都会被类型系统严格约束DeepKeys深层键推导实现类型安全。onSubmit提交回调{ value }即当前表单数据。示例中仅用alert(JSON.stringify(value))展示数据真实项目中可在这里调用 API 接口。注意 onSubmit 被声明为asyncTanStack Form 会等待其完成后才结束提交状态这在 FormApi.ts 的提交流程中体现。四、form.Field 与校验器同步校验 防抖异步校验表单实例的form.Field是一个可嵌套组件通过name绑定数据路径通过validators声明校验规则。先看第一个字段的完整写法form.Field namefirstName validators{{ onChange: ({ value }) value.length 3 ? Not long enough : undefined, onChangeAsyncDebounceMs: 500, onChangeAsync: async ({ value }) { await new Promise((resolve) setTimeout(resolve, 1000)) return value.includes(error) No error allowed in first name }, }} {#snippet children(field)} div label for{field.name}First Name/label input id{field.name} typetext placeholderFirst Name value{field.state.value} onblur{() field.handleBlur()} oninput{(e: Event) { const target e.target as HTMLInputElement field.handleChange(target.value) }} / FieldInfo {field} / /div {/snippet} /form.Field拆解这段代码它同时示范了 TanStack Form 的几个核心概念校验器validators的返回约定每个校验函数返回undefined/null表示通过返回字符串即为错误信息。这里onChange校验firstName长度不得小于 3否则返回Not long enough。异步校验与防抖onChangeAsyncDebounceMs: 500表示用户停止输入 500ms 后才触发异步校验避免每次按键都发请求onChangeAsync则是一个真实模拟异步操作的校验器——等待 1000ms 后若值包含error子串则返回错误信息。这个组合是真实项目中输入停顿→请求服务端校验的标准范式。Snippet 插槽接收字段 APISvelte 5 使用{#snippet children(field)}接收字段实例。field的类型是FieldApi它同时暴露了状态field.state.value、field.state.meta与操作field.handleChange、field.handleBlurUI 完全由你掌控——这正是 TanStack Form headless无头 理念的体现它只管理状态与校验不渲染任何 DOM。从底层实现看Field.svelte 中的createField工厂函数在 Svelte 侧做了三件事new FieldApi(options)实例化核心字段 API并在onMount时调用api.mount()卸载时执行清理函数释放 meta第 82-89 行通过useSelector对api.store建立细粒度的响应式订阅——分别订阅value、meta.isTouched、meta.isBlurred、meta.isDirty、meta.errorMap、meta.errorSourceMap、meta.isValidating第 99-121 行用Object.defineProperty为扩展 API 定义state的 getter把所有订阅的响应式源聚合为可被 Svelte 追踪的field.state第 122-153 行。这就是模板中直接读取field.state.value、field.state.meta.errors就能自动响应更新的原因。五、FieldInfo把 meta 渲染成错误提示示例把错误展示抽成了独立组件 FieldInfo.svelte这段代码很值得收藏因为它展示了字段元信息meta的正确用法script langts import type { AnyFieldApi } from tanstack/svelte-form let { field }: { field: AnyFieldApi } $props() /script {#if field.state.meta.isTouched} {#each field.state.meta.errors as error} em{error}/em {/each} {field.state.meta.isValidating ? Validating... : } {/if}要点说明AnyFieldApi是官方导出的任意字段统一类型适合做通用子组件field.state.meta.isTouched表示字段是否被用户触碰过blur 后为true因此错误信息只在触碰后才显示避免初始状态就满屏报错field.state.meta.errors是当前生效的错误数组用{#each}遍历渲染field.state.meta.isValidating在异步校验进行中为true此时展示Validating...提示配合异步校验器可以做出校验中的加载反馈。这个组件的触发链路依赖上一节提到的onblur{() field.handleBlur()}用户离开输入框 →handleBlur更新isTouched并触发 blur 校验 → 订阅了 meta 的stategetter 重新求值 → 模板重渲染错误信息。六、受控输入与复选框绑定 state 的两种形态文本输入采用标准的值绑定 事件驱动模式input value{field.state.value} oninput{(e: Event) { const target e.target as HTMLInputElement field.handleChange(target.value) }} onblur{() field.handleBlur()} /field.state.value驱动显示field.handleChange(target.value)回写状态并触发onChange校验field.handleBlur()标记触碰状态。复选框则利用布尔值直接取反form.Field nameemployed {#snippet children(field)} div label for{field.name}Employed?/label input oninput{() field.handleChange(!field.state.value)} checked{field.state.value} onblur{() field.handleBlur()} id{field.name} typecheckbox / /div ... {/snippet} /form.FieldhandleChange(!field.state.value)每次点击时写入相反值checked属性负责回显。注意employed字段没有配置validators说明校验器是可选配置。七、条件字段根据状态动态渲染子字段示例最精彩的部分是条件字段——只有勾选了 Employed? 复选框才显示 Job Title 输入框且该输入框有必填校验{#if field.state.value} form.Field namejobTitle validators{{ onChange: ({ value }) value.length 0 ? If you have a job, you need a title : null, }} {#snippet children(field)} div label for{field.name}Job Title/label input typetext id{field.name} placeholderJob Title value{field.state.value} onblur{field.handleBlur} oninput{(e: Event) { const target e.target as HTMLInputElement field.handleChange(target.value) }} / FieldInfo {field} / /div {/snippet} /form.Field {/if}这里的关键机制外层field是employed字段{#if field.state.value}用其响应式状态控制内部字段的挂载与卸载form.Field是可嵌套的——内层字段的namejobTitle是相对整个表单数据根路径的defaultValues.jobTitleTanStack Form 支持任意层级的字段嵌套无需手动管理父子关系该字段的校验器规定jobTitle为空时返回错误If you have a job, you need a title。由于canSubmit会自动汇总所有已挂载字段的错误状态未勾选 Employed? 时jobTitle字段根本不存在自然不会阻塞提交一旦勾选它就参与校验并影响提交可用性。这正是 composition 示例 在 React 侧演示的同一能力——字段的组合与解构完全由渲染逻辑决定。八、form.Subscribe 与提交按钮canSubmit / isSubmitting / reset表单提交区用form.Subscribe订阅表单级状态div form.Subscribe selector{(state) ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting, })} {#snippet children({ canSubmit, isSubmitting })} button typesubmit disabled{!canSubmit} {isSubmitting ? Submitting : Submit} /button {/snippet} /form.Subscribe button typebutton idreset onclick{() { form.reset() }} Reset /button /divselector 派生订阅selector从整个表单状态中只挑选canSubmit能否提交由所有字段校验与表单校验共同决定和isSubmitting是否正在提交两个字段形成派生状态对象。提交按钮在!canSubmit时禁用提交过程中文案切换为Submitting。这种按需订阅是 TanStack Form 性能设计的一部分——只有被 selector 选中的状态变化才会触发重渲染。底层 Subscribe.svelte 实现非常薄它调用useSelector(store, selector)获取派生值再{render children(value.current)}渲染传入的 snippet。表单提交链路form元素的onsubmit处理器如下form idform onsubmit{(e) { e.preventDefault() e.stopPropagation() form.handleSubmit() }} handleSubmit()会依次执行字段校验 → 表单级校验若配置了validators→ 置isSubmitting为true→ 调用onSubmit→ 完成后复位isSubmitting。重置form.reset()一键把表单数据恢复为defaultValues同时清空字段的触碰/错误等 meta 状态。九、运行效果与学习路径执行npm install npm run dev后浏览器会打开一个TanStack Form - Svelte Demo页面输入少于 3 个字符的姓名会立刻提示Not long enough在 First Name 中输入包含error的文本停顿 500ms 后进入 1 秒的异步校验并显示Validating...随后提示No error allowed in first name勾选 Employed? 出现 Job Title 必填项提交按钮在存在任何错误时禁用提交期间文案变为SubmittingReset 按钮可一键还原表单。这个 100 行出头的示例实际上覆盖了 TanStack Form 在 Svelte 中最常用的一整套 API。进一步学习可以沿着以下路径深入仓库框架无关的核心状态机packages/form-core/src/FormApi.ts 与 packages/form-core/src/FieldApi.ts理解handleChange、handleBlur、校验队列的实现Svelte 适配层完整源码packages/svelte-form/src/createForm.svelte.ts、Field.svelte、Subscribe.svelte更多实战场景仓库 examples/svelte 下还提供了array数组字段、large-form大型表单性能、multi-step-wizard多步向导、standard-schema标准 Schema 校验等示例官方文档入口Svelte 快速上手、Svelte 指南目录、Svelte 参考目录。从最小示例出发逐步对照源码阅读是理解 TanStack Form 这套框架无关核心 各框架薄适配层架构最有效的路径。【免费下载链接】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),仅供参考