尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深入解析 @tanstack/vue-form 的 FormGroupComponent 类型:泛型参数、插槽契约与子表单实现

深入解析 @tanstack/vue-form 的 FormGroupComponent 类型:泛型参数、插槽契约与子表单实现 深入解析 tanstack/vue-form 的 FormGroupComponent 类型泛型参数、插槽契约与子表单实现【免费下载链接】form Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, and Lit.项目地址: https://gitcode.com/GitHub_Trending/form/formFormGroupComponent 是 tanstack/vue-form 中form.FormGroup子表单组件对应的类型别名它把 TanStack Form 的表单分组Form Group能力以类型安全的方式暴露给 Vue 的模板与 setup 函数。本文基于仓库中的 API 参考文档结合 packages/vue-form/src/useFormGroup.tsx 的源码实现与 docs/framework/vue/guides/form-groups.md 的使用指南完整拆解它的 22 个类型参数、props 与返回值契约、default 插槽提供的group/state并给出可在多步骤向导场景直接落地的实战示例。一、FormGroupComponent 是什么类型别名全貌FormGroupComponent 是 docs/framework/vue/reference/type-aliases/FormGroupComponent.md 中定义的泛型类型别名它描述的是useForm()返回的form对象上挂载的FormGroup组件的完整类型签名。在 Vue 中它表现为一个构造函数类型new (...)形式接收 props 并返回一个 Vue 组件公开实例CreateComponentPublicInstanceWithMixins。该类型在源码中定义于 packages/vue-form/src/useFormGroup.tsx#L24-L189其核心形态如下为便于阅读省略了部分展开export type FormGroupComponent TParentData, TFormOnMount, ... TParentSubmitMeta, // 12 个表单级泛型来自父表单 new TName extends DeepKeysTParentData, TData extends DeepValueTParentData, TName, TOnMount, TOnChange, ... TSubmitMeta, // 10 个分组级泛型 ( props: FormGroupComponentBoundProps... EmitsToPropsEmitsOptions PublicProps, ) CreateComponentPublicInstanceWithMixins FormGroupComponentBoundProps..., ..., SlotsType{ default: { group: FormGroupApi... state: FormGroupApi...[state] } } 需要特别注意它的双重泛型结构外层 12 个泛型TParentData、TFormOnMount至TParentSubmitMeta代表父表单由useForm创建的类型信息在form.FormGroup被访问时已经预先绑定内层 10 个泛型TName、TData、TOnMount至TSubmitMeta保持开放交由 Vue 在渲染组件时根据 props 自动推断。源码注释明确说明了这一设计意图This allows us to pre-bind some generics while keeping the props type unbound generics for props-based inferencing预绑定部分泛型同时保留 props 侧的未绑定泛型以便基于 props 推断见 packages/vue-form/src/useFormGroup.tsx#L37-L38。这正是 FormGroupComponent 能够在模板中既享受父表单类型信息、又能根据name与validators动态收紧子表单类型的关键。二、22 个类型参数逐一拆解FormGroupComponent 的全部类型参数可划分为表单级与分组级两组下面按参考文档逐项说明约束条件。表单级参数12 个外层预绑定这些泛型描述了创建该分组所属的父表单FormApi时使用的类型约束均以TParentData为基准参数约束含义TParentData无约束父表单的完整数据结构TFormOnMountundefined \| FormValidateOrFnTParentData父表单 mount 校验函数TFormOnChangeundefined \| FormValidateOrFnTParentData父表单 change 校验函数TFormOnChangeAsyncundefined \| FormAsyncValidateOrFnTParentData父表单 change 异步校验函数TFormOnBlurundefined \| FormValidateOrFnTParentData父表单 blur 校验函数TFormOnBlurAsyncundefined \| FormAsyncValidateOrFnTParentData父表单 blur 异步校验函数TFormOnSubmitundefined \| FormValidateOrFnTParentData父表单 submit 校验函数TFormOnSubmitAsyncundefined \| FormAsyncValidateOrFnTParentData父表单 submit 异步校验函数TFormOnDynamicundefined \| FormValidateOrFnTParentData父表单动态校验函数TFormOnDynamicAsyncundefined \| FormAsyncValidateOrFnTParentData父表单动态异步校验函数TFormOnServerundefined \| FormAsyncValidateOrFnTParentData父表单服务端校验函数TParentSubmitMeta无约束父表单提交时携带的元数据类型其中FormValidateOrFnT表示“同步校验函数或标准 Schema”的联合类型FormAsyncValidateOrFnT则是其异步版本二者定义于 packages/form-core/src/FormApi.ts。分组级参数10 个内层按 props 推断这组泛型决定了当前分组在父数据中所指向的切片及其自身的校验行为参数约束含义TNameDeepKeysTParentData分组在父数据中的深路径键名如step1TDataDeepValueTParentData, TName该路径对应的值类型TOnMountundefined \| FormGroupValidateOrFnTParentData, TName, TData分组 mount 校验函数TOnChangeundefined \| FormGroupValidateOrFnTParentData, TName, TData分组 change 校验函数TOnChangeAsyncundefined \| FormGroupAsyncValidateOrFnTParentData, TName, TData分组 change 异步校验函数TOnBlurundefined \| FormGroupValidateOrFnTParentData, TName, TData分组 blur 校验函数TOnBlurAsyncundefined \| FormGroupAsyncValidateOrFnTParentData, TName, TData分组 blur 异步校验函数TOnSubmitundefined \| FormGroupValidateOrFnTParentData, TName, TData分组 submit 校验函数TOnSubmitAsyncundefined \| FormGroupAsyncValidateOrFnTParentData, TName, TData分组 submit 异步校验函数TOnDynamic/TOnDynamicAsync同上对应类型分组动态校验含异步版本TSubmitMeta无约束分组提交元数据类型分组级校验函数类型FormGroupValidateOrFn与FormGroupAsyncValidateOrFn定义于 packages/form-core/src/FormGroupApi.ts#L100-L157它们既可以是一个接收{ value, groupApi }的函数也可以是StandardSchemaV1TData, unknown标准 Schema如 zod schema异步版本还会额外收到signal: AbortSignal以支持请求取消。三、props 与返回值的类型契约props三部分拼接FormGroupComponent 的 props 由三部分交叉拼接而成见 docs/framework/vue/reference/type-aliases/FormGroupComponent.md 的 Parameters 一节FormGroupComponentBoundProps... EmitsToPropsEmitsOptions PublicProps其中FormGroupComponentBoundProps分组全部可配置选项。它在 packages/vue-form/src/useFormGroup.tsx#L384-L448 中被定义为FormGroupOptions...的别名而FormGroupOptions继承自FieldLikeOptions含name、validators、listeners、defaultValue等与FormGroupExtraOptions含canSubmitWhenInvalid、validationLogic、onGroupSubmit、onGroupSubmitInvalid、onSubmitMeta等见 packages/form-core/src/FormGroupApi.ts#L468-L560EmitsToPropsEmitsOptionsVue 事件声明转 props 的辅助类型PublicPropsVue 组件通用公共 props如class、style、key等。需要区分的是FormGroupComponentProps 是FormGroupApiOptions的别名它在FormGroupOptions基础上额外要求一个form: FormApi...属性定义于 packages/form-core/src/FormGroupApi.ts#L639-L652而FormGroupComponentBoundProps不包含form——因为form.FormGroup经由VueFormGroupApi接口预绑定了父表单用户无需在模板中手动传入form。这个区别正是组件绑定后 props 自动收窄的体现。返回值Vue 组件公开实例 插槽类型返回值是CreateComponentPublicInstanceWithMixinsFormGroupComponentBoundProps..., ...其中通过SlotsType声明了 default 插槽的渲染参数见 packages/vue-form/src/useFormGroup.tsx#L133-L188SlotsType{ default: { group: FormGroupApiTParentData, TName, TData, ... state: FormGroupApiTParentData, TName, TData, ...[state] } }这意味着在模板的v-slot{ group, state }中Vue 与 TypeScript 都能准确推断出group与state的完整类型。四、default 插槽契约group 与 statev-slot暴露的两个插槽属性是使用 FormGroup 的全部入口group: FormGroupApi...分组 API 实例拥有与FieldApi/FormApi类似的方法集。参考文档与示例注释表明它具备handleSubmit()、deleteField、insertFieldValue等“表单级”方法见 docs/framework/vue/guides/form-groups.md 的 Usage 一节state: FormGroupApi...[state]分组的响应式状态类型为ReadonlyRefFormGroupStoreState...。FormGroupStoreState包含value分组当前值与meta分组元信息两个字段定义于 packages/form-core/src/FormGroupApi.ts#L859-L939。group与state正是useFormGroup的返回值——docs/framework/vue/reference/functions/useFormGroup.md 中声明其返回{ api, state }只读对象其中state由useSelector(formGroupApi.store, (state) state)派生而来见 packages/vue-form/src/useFormGroup.tsx#L296。FormGroupComponent类型中state被展开为FormGroupApi[...][state]索引访问类型保证了插槽参数与FormGroupApi实例内部状态类型永远保持一致不会出现手写类型导致的漂移。五、源码实现defineComponent 与生命周期FormGroup组件本体是defineComponent创建的渲染函数组件源码位于 packages/vue-form/src/useFormGroup.tsx#L450-L531export const FormGroup defineComponent( (formGroupOptions, context: SetupContext) { const groupApi useFormGroup({ ...formGroupOptions, ...context.attrs }) return () context.slots.default!({ group: groupApi.api, state: groupApi.state.value, }) }, { name: FormGroup, inheritAttrs: false }, )其底层useFormGrouppackages/vue-form/src/useFormGroup.tsx#L221-L316负责完整生命周期创建实例new FormGroupApi({ ...opts })构建分组核心 API订阅状态通过useSelector(formGroupApi.store, (state) state)将 TanStack Store 的状态桥接为 Vue 响应式引用挂载/卸载onMounted中调用formGroupApi.mount()并保存返回的清理函数onUnmounted时执行清理见 packages/vue-form/src/useFormGroup.tsx#L299-L305选项热更新watch(() opts, ...)在选项变化时调用formGroupApi.update(...)保证组件 props 变化能同步到内部 store见 packages/vue-form/src/useFormGroup.tsx#L307-L313。从源码结构可以推断FormGroupComponent类型别名正是为了精确描述上述渲染函数组件的类型而存在的——它把defineComponent的泛型签名、插槽类型与 FormGroup 专属的 props 结构固化下来供 VueFormGroupApi 接口见 packages/vue-form/src/useFormGroup.tsx#L191-L219引用最终成为useForm()返回值上的form.FormGroup。六、实战多步骤向导中的 FormGroupFormGroupComponent 的核心应用场景是多步骤分阶段表单当每个步骤都需要独立的校验与提交行为时form.FormGroup可以构造出“子表单”避免手写复杂的步骤状态机。以下示例取自 examples/vue/multi-step-wizard/src/App.vue是仓库中最完整的实战范本。注意form.FormGroup上的name必须是父数据中的深路径键script setup langts import { revalidateLogic, useForm } from tanstack/vue-form import { ref } from vue import { z } from zod const step ref(0) const form useForm({ defaultValues: { step1: { name: }, step2: { age: 0 }, }, validationLogic: revalidateLogic(), validators: { // onDynamic 只在 form.handleSubmit 被直接调用时生效 onDynamic: z.object({ step1: z.object({ name: z.string().min(2) }), step2: z.object({ age: z.number().min(18) }), }), }, onSubmit: ({ value }) { alert(Form submitted: ${JSON.stringify(value)}) }, }) const onGroupSubmit () { step.value } const onGroupSubmitInvalid () { // 分组级无效提交处理可阻止进入下一步 } /script template form.FormGroup v-ifstep 0 namestep1 :validators{ onDynamic: step1Schema } :onGroupSubmitonGroupSubmit :onGroupSubmitInvalidonGroupSubmitInvalid v-slot{ group: formGroup } form submit.prevent.stopformGroup.handleSubmit() !-- 分组内部通过 formGroup.handleSubmit() 只提交当前子表单 -- form.Field namestep1.name v-slot{ field } input :valuefield.state.value inputfield.handleChange(($event.target as HTMLInputElement).value) blurfield.handleBlur() / /form.Field pre{{ JSON.stringify(formGroup.state.meta.errorMap, null, 2) }}/pre /form /form.FormGroup form.FormGroup v-ifstep 1 namestep2 :validators{ onDynamic: step2Schema } :onGroupSubmit() form.handleSubmit() v-slot{ group: formGroup } !-- 最后一步再调用 form.handleSubmit() 提交整个表单 -- /form.FormGroup /template关键要点每个FormGroup都是独立可提交的子表单formGroup.handleSubmit()只校验并提交当前分组而form.handleSubmit()提交整个父表单分组级提交回调onGroupSubmit/onGroupSubmitInvalid与顶层表单的onSubmit/onSubmitInvalid语义一致非常适合“校验通过才进入下一步”的向导逻辑v-slot解构出的formGroup与formGroup.state的类型正是由 FormGroupComponent 的插槽契约保证的。分组校验的三种写法按 docs/framework/vue/guides/form-groups.md 的 Form Group Validation 一节validators支持三种形式1. 分组自有同步校验form.FormGroup namestep1 :validators{ onChange: () Error } v-slot{ group: formGroup } !-- formGroup.state.meta.errorMap // { onChange: Error | undefined } -- !-- formGroup.state.meta.errors // (Error)[] -- /form.FormGroup2. 对子字段设置错误错误键必须使用相对分组的字段名而非全路径form.FormGroup namestep1 :validators{ onChange: ({ value, groupApi }) ({ group: value.name error ? Group error : undefined, fields: { name: value.name error ? Field error : undefined, }, }), } /3. 直接接收标准 Schema如 zodform.FormGroup namestep1 :validators{ onChange: z.object({ name: z.string().min(2) }) } /使用相对字段名是为了保证 Schema 的可组合性可以把step1Schema传给分组、把整体schema z.object({ step1: step1Schema, step2: step2Schema })传给父表单二者共用同一份子 Schema即使跳过某个分组也能在整体校验时正确报错。动态分组校验的注意事项若在分组上使用动态校验onDynamic不要把子表单 Schema 传给useForm的onDynamic——那样它只会在父表单提交时触发而应像上面的示例那样把子 Schema 传给FormGroup自身的:validators{ onDynamic: step1Schema }。分组会根据formGroup.submissionAttempts决定提交前后运行哪套校验相关细节见 docs/framework/vue/guides/dynamic-validation.md。分组状态读取在formGroup.state.meta上可直接读取以下派生状态见 docs/framework/vue/guides/form-groups.md 的 Form Group State 一节isFieldsValid所有字段级校验均无错误时为trueisGroupValid所有分组级校验均无错误时为trueisValid字段级与分组级校验均无错误时为trueisSubmitting分组提交进行中为trueformGroup.state.value分组当前值。七、类型家族FormGroupComponent 与周边类型的关系FormGroupComponent 并非孤立存在它处于一个完整类型家族的中心全部见 docs/framework/vue/reference/index.mdFormGroupComponentBoundPropsFormGroupOptions的别名即分组组件的全部可配置选项是 props 的主体FormGroupComponentPropsFormGroupApiOptions的别名额外要求form属性用于描述未经form绑定的原始组件形态FormGroupdefineComponent产出的实际组件变量其泛型签名与 FormGroupComponent 一一对应VueFormGroupApi接口声明FormGroup: FormGroupComponent...属性useForm()返回值中的FormGroup即由此而来useFormGroup组合式函数返回{ api, state }是组件插槽数据的生产者。从源码结构看这条类型链的设计目标可以概括为用一套泛型参数表单级 12 个 分组级 10 个贯穿useForm返回值、组件 props 校验、插槽渲染参数与底层FormGroupApi实例让开发者无论写模板还是写脚本都能获得与运行时行为完全一致的静态类型提示。八、总结FormGroupComponent 是 tanstack/vue-form 类型体系中最具代表性的“桥接”类型之一它用 22 个泛型参数把父表单与子分组的信息完整编码外层 12 个在form.FormGroup访问时预绑定、内层 10 个交由 props 推断它的 props 由FormGroupComponentBoundProps、EmitsToProps与PublicProps交叉构成返回值通过SlotsType声明v-slot的group与state二者分别对应FormGroupApi实例及其响应式状态在源码层它精确描述了 useFormGroup.tsx 中defineComponent渲染函数组件的形态并依托useSelector、mount/unmount与watch实现完整的 Vue 响应式生命周期。理解了 FormGroupComponent也就理解了form.FormGroup在模板中为何能获得如此精确的类型推断。对于需要实现多步骤向导、分阶段校验或复杂子表单结构的 Vue 应用它是把 TanStack Form 的表单分组能力安全接入类型系统的最关键一环建议结合 examples/vue/multi-step-wizard/src/App.vue 与 docs/framework/vue/guides/form-groups.md 对照阅读。【免费下载链接】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),仅供参考
返回列表