
TanStack Form 的 FormState 解析表单状态接口的完整属性、类型参数与派生机制【免费下载链接】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本文围绕 TanStack Form 的类型参考文档docs/reference/interfaces/FormState.md展开系统梳理FormState接口承载的全部状态属性values、errorMap、canSubmit、isSubmitting等 25 个字段、11 个泛型类型参数的含义以及它们在form-core源码中如何由基础存储baseStore派生计算、如何驱动 React 等框架适配层的 UI 更新。读完后可准确理解该库「基础状态 派生状态」的两层状态模型并能在实际表单 UI 中正确消费这些状态字段。1. FormState 是什么表单当前状态的唯一事实来源FormState是 TanStack Form 中表示表单当前状态的接口定义于 FormApi.ts第 793 行起。官方文档中的定义如下interface FormState in out TFormData, in out TOnMount extends undefined | FormValidateOrFnTFormData, in out TOnChange extends undefined | FormValidateOrFnTFormData, in out TOnChangeAsync extends undefined | FormAsyncValidateOrFnTFormData, in out TOnBlur extends undefined | FormValidateOrFnTFormData, in out TOnBlurAsync extends undefined | FormAsyncValidateOrFnTFormData, in out TOnSubmit extends undefined | FormValidateOrFnTFormData, in out TOnSubmitAsync extends undefined | FormAsyncValidateOrFnTFormData, in out TOnDynamic extends undefined | FormValidateOrFnTFormData, in out TOnDynamicAsync extends undefined | FormAsyncValidateOrFnTFormData, in out TOnServer extends undefined | FormAsyncValidateOrFnTFormData, extends BaseFormState..., DerivedFormState... {}从源码结构看FormState本身是一个空接口全部能力来自它对两个类型别名组合接口的继承BaseFormStateFormApi.ts 第 613 行起由FormApi直接写入和维护的原始状态——字段值、错误映射、提交生命周期标志、字段元数据底稿等DerivedFormStateFormApi.ts 第 713 行起每次基础状态变化时重新计算得出的派生状态——各类is*布尔聚合标志、errors数组、canSubmit等。这个「基础 派生」的分层是理解 TanStack Form 状态模型的关键框架层React/Vue/Solid/Angular 等通过订阅FormApi内部的 store 读取到的就是FormState而其中派生字段永远与基础字段保持一致因为它们在同一个 store 更新函数内被原子地重新计算见第 5 节。1.1 泛型类型参数Type ParametersFormState共有 11 个泛型参数全部使用in out双向变型修饰方便在框架适配层中以协变/逆变方式传递类型参数约束说明TFormData无表单数据对象的类型values的类型由此决定TOnMountundefined \| FormValidateOrFnTFormDataonMount校验器的返回类型决定errors/errorMap中对应槽位的元素类型TOnChangeundefined \| FormValidateOrFnTFormDataonChange同步校验器TOnChangeAsyncundefined \| FormAsyncValidateOrFnTFormDataonChange异步校验器TOnBlurundefined \| FormValidateOrFnTFormDataonBlur同步校验器TOnBlurAsyncundefined \| FormAsyncValidateOrFnTFormDataonBlur异步校验器TOnSubmitundefined \| FormValidateOrFnTFormDataonSubmit同步校验器TOnSubmitAsyncundefined \| FormAsyncValidateOrFnTFormDataonSubmit异步校验器TOnDynamicundefined \| FormValidateOrFnTFormDataonDynamic同步校验器TOnDynamicAsyncundefined \| FormAsyncValidateOrFnTFormDataonDynamic异步校验器TOnServerundefined \| FormAsyncValidateOrFnTFormData服务端校验结果槽位仅允许异步函数类型这些校验器泛型并非装饰性类型errorMap与errors的每个元素类型都由UnwrapFormValidateOrFnTOnMount/UnwrapFormAsyncValidateOrFnTOnServer等工具类型见 util-types.ts从对应泛型中「解包」得出。也就是说你在FormOptions中写了onSubmit: ({ value }) Too young那么state.errors里就会获得可精确补全的字符串类型而非宽泛的unknown。2. BaseFormStateFormApi 直接维护的基础状态以下是FormState继承自 BaseFormState 的全部属性源码位置在 FormApi.ts。2.1 values — 字段当前值values: TFormData所有字段当前值的聚合对象第 629 行。它是FormApi的起点创建表单时通过defaultValues选项写入之后任何field.handleChange都会更新其中对应字段。2.2 errorMap — 表单级错误映射errorMap: ValidationErrorMap UnwrapFormValidateOrFnTOnMount, UnwrapFormValidateOrFnTOnChange, UnwrapFormAsyncValidateOrFnTOnChangeAsync, UnwrapFormValidateOrFnTOnBlur, UnwrapFormAsyncValidateOrFnTOnBlurAsync, UnwrapFormValidateOrFnTOnSubmit, UnwrapFormAsyncValidateOrFnTOnSubmitAsync, UnwrapFormValidateOrFnTOnDynamic, UnwrapFormAsyncValidateOrFnTOnDynamicAsync, UnwrapFormAsyncValidateOrFnTOnServer 表单自身而非字段的错误映射第 633 行。键是校验时机onMount/onChange/onBlur/onSubmit/onDynamic/onServer值是该时机下校验函数返回的错误值没有错误时为undefined。从源码看errorMap的每个槽位只保留同一时机下最近一次校验的结果新校验会覆盖旧结果。2.3 validationMetaMap — 校验内部元信息validationMetaMap: RecordValidationErrorMapKeys, ValidationMeta | undefined第 648 行。官方注释明确其为「内部机制不面向公开使用」。每个ValidationMetaFormApi.ts持有一个lastAbortController: AbortController用于在新一轮异步校验开始时取消上一轮尚未完成的异步校验请求从而保证快速连续输入时旧请求的错误不会覆盖新请求的结果。2.4 fieldMetaBase — 字段元数据底稿fieldMetaBase: PartialRecordDeepKeysTFormData, AnyFieldLikeMetaBase第 652 行。以字段的深层键DeepKeysTFormData支持a.b.c这类嵌套/数组路径为键存储每个字段不含派生属性的元数据如isTouched、isBlurred、isDirty等布尔标志与errorMap。文档原文强调这里「不包含errors之类的派生属性」——派生后的完整字段元数据在FormState.fieldMeta中见 3.6。2.5 formGroupStateBase — 字段组生命周期状态formGroupStateBase: PartialRecordstring, FormGroupState第 659 行。按字段组的全限定字段名为键存储每个已挂载FormGroupApi的提交生命周期状态。源码注释解释了它存放在表单上的原因让FormApi无需遍历已挂载的组实例即可读取组级状态。2.6 提交生命周期三兄弟isSubmitting / isSubmitted / submissionAttemptsisSubmitting: boolean // 第 672 行 isSubmitted: boolean // 第 680 行 submissionAttempts: number // 第 688 行 isSubmitSuccessful: boolean // 第 692 行isSubmitting调用handleSubmit后进入true当「校验返回了错误」或「onSubmit函数执行完毕」时回到false。官方文档特别提醒如果在onSubmit里运行异步操作务必await它们否则isSubmitting会在异步操作完成前提前复位。典型用途是提交期间显示 loading 或禁用输入框。isSubmittedonSubmit函数成功完成后为true每次新的提交尝试都会先复位为false。submissionAttempts提交尝试计数器从 0 开始。它在源码中还有两个实际作用参与canSubmit的计算见 3.5以及在handleSubmit中区分「首次提交」与「重复提交」——FormApi.ts 第 2452 行处当canSubmit为false时只有submissionAttempts 1才直接触发onSubmitInvalid并提前返回重复提交则会继续走validateAllFields以便重新校验并清除上一轮残留的过期字段错误例如某字段已不在onBlur校验范围内。isSubmitSuccessful上一次提交是否成功。这些标志位都通过baseStore.setState直接写入例如提交开始时FormApi.tsthis.baseStore.setState((d) ({ ...d, isSubmitting: true })) const done () { this.baseStore.setState((prev) ({ ...prev, isSubmitting: false })) }2.7 isValidating 与 _force_re_eval私有isValidating: boolean // 第 684 行表单或任一字段正在校验 _force_re_eval?: boolean // 第 696 行private_force_re_eval是唯一标记为private的状态字段官方注释为「当 options 变化时用于强制重新求值表单状态」。从源码结构看transform.ts 在 diff 用户transform回调改动的状态时把_force_re_eval列入BaseFormState的完整键清单参与逐键对比确保transform返回的状态能逐项同步回baseStore。业务代码不应读写该字段。3. DerivedFormState每次状态变化重新计算的派生字段继承自 DerivedFormState 的属性FormApi.ts不是直接存储的而是FormApi构造 store 时从基础状态与字段元数据中计算得出的。逐字段说明如下。3.1 isFormValidating / isFieldsValidating — 表单级与字段级的校验中标志isFormValidating: boolean // 第 729 行表单自身是否正在校验 isFieldsValidating: boolean // 第 754 行任一字段是否正在校验二者的聚合规则在 FormApi.ts 中一目了然isFieldsValidating取所有字段meta.isValidating的some任一为真即真而BaseFormState.isValidating2.7 节则是表单级校验中的标志。二者共同构成「整棵表单正在校验」的判断基础。3.2 errors — 表单级错误数组errors: NonNullable | UnwrapFormValidateOrFnTOnMount | UnwrapFormValidateOrFnTOnChange | /* ... 各时机类型 ... */ | UnwrapFormAsyncValidateOrFnTOnServer []第 737 行。errorMap的扁平化版本把各时机槽位中非undefined的错误收集成数组。源码中FormApi.ts对它的构建有两点工程细节值得注意引用保持注释写明「errors不是原始值出于性能考虑需要积极保持同一引用」。即只有当errorMap引用本身变化时才重新 reduce否则复用上一轮的errors数组引用避免框架层因引用变化触发不必要的渲染/响应式更新全局表单错误解包若某错误满足isGlobalFormValidationError形如{ form: ... }的全局表单错误包装则推入其form字段而非包装对象本身。3.3 isFormValid / isFieldsValid / isValid — 三层有效性isFormValid: boolean // 第 733 行errors.length 0 isFieldsValid: boolean // 第 758 行所有字段均无错误 isValid: boolean // 第 782 行isFieldsValid isFormValid源码中的计算FormApi.tsconst isFieldsValid fieldMetaValues.every((field) field.isValid) const isFormValid errors.length 0 const isValid isFieldsValid isFormValid注意语义分层isFormValid只看表单级校验器isFieldsValid只看字段isValid是两者的与。UI 上「表单整体是否合法」应使用isValid。3.4 isTouched / isBlurred / isDirty / isPristine / isDefaultValue — 字段交互状态聚合isTouched: boolean // 第 762 行任一字段被 touch isBlurred: boolean // 第 766 行任一字段发生过 blur isDirty: boolean // 第 770 行至少一个字段的值被用户修改 isPristine: boolean // 第 774 行isDirty 的反义 isDefaultValue: boolean // 第 778 行所有字段值都等于默认值源码中的聚合规则FormApi.tsconst isTouched fieldMetaValues.some((field) field.isTouched) const isBlurred fieldMetaValues.some((field) field.isBlurred) const isDirty fieldMetaValues.some((field) field.isDirty) const isPristine !isDirty const isDefaultValue fieldMetaValues.every((field) field.isDefaultValue)即isTouched/isBlurred/isDirty用some任一即可isDefaultValue用every全部满足。3.5 canSubmit — 能否提交canSubmit: boolean // 第 786 行这是 UI 中控制提交按钮禁用状态的核心字段其完整计算逻辑FormApi.ts为const submitInvalid this.options.canSubmitWhenInvalid ?? false const canSubmit (currBaseStore.submissionAttempts 0 !isTouched !hasOnMountError) || (!isValidating !currBaseStore.isSubmitting isValid) || submitInvalid拆成三种情况理解未交互的新表单从未尝试提交、没有任何字段被 touch、且无onMount错误时默认可提交避免用户还没动过表单按钮就被禁用常规路径不在校验中、不在提交中、且isValid为真canSubmitWhenInvalid逃生舱选项canSubmitWhenInvalid: trueFormApi.ts时无条件为true允许在表单无效状态下发起提交——配合onSubmitInvalid回调使用适合「用户提交后才展示所有错误」的交互模式。canSubmit不只是 UI 信号handleSubmit内部FormApi.ts会先检查state.canSubmit为false且属首次提交时直接调用onSubmitInvalid并返回不会进入字段校验流程。3.6 fieldMeta — 派生后的完整字段元数据fieldMeta: PartialRecordDeepKeysTFormData, AnyFieldLikeMeta第 790 行。与fieldMetaBase底稿不含派生属性相对fieldMeta中每个字段的AnyFieldLikeMeta包含errors等派生属性由独立的fieldMetaDerivedstore 从fieldMetaBase计算得出FormApi.ts再被组装进最终的FormState。字段级 UI错误提示、校验中 loading通常消费字段自己的field.state.meta而form.state.fieldMeta则用于表单级逻辑例如 3.1/3.4 中所有is*聚合。4. 状态如何初始化getDefaultFormState 中的默认值FormState各字段的初始值由 getDefaultFormState 提供return { values: defaultState.values ?? ({} as never), errorMap: defaultState.errorMap ?? {}, fieldMetaBase: defaultState.fieldMetaBase ?? ({} as never), formGroupStateBase: defaultState.formGroupStateBase ?? {}, isSubmitted: defaultState.isSubmitted ?? false, isSubmitting: defaultState.isSubmitting ?? false, isValidating: defaultState.isValidating ?? false, submissionAttempts: defaultState.submissionAttempts ?? 0, isSubmitSuccessful: defaultState.isSubmitSuccessful ?? false, validationMetaMap: defaultState.validationMetaMap ?? { onChange: undefined, onBlur: undefined, onSubmit: undefined, onMount: undefined, onServer: undefined, onDynamic: undefined, }, }要点validationMetaMap预置了 6 个校验时机槽位onChange/onBlur/onSubmit/onMount/onServer/onDynamic与 2.2 节errorMap的键域一致所有布尔标志初始为false、submissionAttempts初始为0。该函数只产出BaseFormState部分派生字段errors、isValid等由 store 的首次求值补齐。5. 派生机制深潜FormState 在一个 store 中原子更新FormApi内部用两个 store 组织状态baseStore存BaseFormStateFormApi.ts与对外暴露的store存完整FormStateFormApi.ts。对外 store 的更新函数把 3 节所有派生字段的计算串联在一起并在末尾做逐字段引用比较if ( prevVal prevBaseStoreForStore prevVal.errorMap errorMap prevVal.fieldMeta this.fieldMetaDerived.state prevVal.errors errors prevVal.isFieldsValidating isFieldsValidating /* ... canSubmit / isTouched / isBlurred / isPristine / isDefaultValue / isDirty 等逐项比较 ... */ evaluate(prevBaseStoreForStore, currBaseStore) ) { return prevVal // 无实质变化 → 返回上一轮对象保持引用稳定 }FormApi.ts这段逻辑的工程含义若一次基础状态更新没有真正改变任何派生结果store 直接返回旧对象引用框架适配层的响应式订阅如 React 的useSyncExternalStore语义因此不会触发多余渲染。此外还有一个细节当isTouched为真且存在onMount错误时源码会执行shouldInvalidateOnMount分支FormApi.ts——用户一旦触碰表单过期的onMount错误会从errors中剔除并将errorMap.onMount置空保证挂载期错误不会干扰后续交互校验。6. 实战在 React 适配层消费 FormState在框架适配层FormState通过form.Subscribe的selector精确订阅避免无关字段变化引发重渲染。官方示例 examples/react/simple/src/index.tsx 展示了最典型的两个消费点form.Subscribe selector{(state) [state.canSubmit, state.isSubmitting]} children{([canSubmit, isSubmitting]) ( button typesubmit disabled{!canSubmit} {isSubmitting ? ... : Submit} /button button typereset onClick{(e) { e.preventDefault() form.reset() }} Reset /button / )} /state.canSubmit→ 控制提交按钮disabled语义与 3.5 节的三条判定规则一致state.isSubmitting→ 控制按钮文案的 loading 态其生命周期由handleSubmit内的baseStore.setState切换2.6 节同一示例中字段级 UI 则消费field.state.meta.isTouched/isValid/isValidatingindex.tsx对应表单级fieldMeta中同一组语义的字段版本。其他框架适配层Vue 的form.subscribe、Svelte 的form.s.state、Solid 的createStore等读取的都是同一个FormState结构因此本文对属性的解释跨框架通用。7. 速查表FormState 全部属性一览属性来源层含义计算/维护方式valuesBase字段当前值FormApi写入errorMapBase表单级各时机错误映射校验完成后按槽位覆盖validationMetaMapBase各时机AbortController等内部元信息内部机制fieldMetaBaseBase字段元数据底稿无派生属性字段交互时写入formGroupStateBaseBase各FormGroupApi的提交生命周期状态组挂载/提交时写入isSubmittingBase正在提交handleSubmit切换isSubmittedBaseonSubmit已完成新提交尝试时复位handleSubmit维护isValidatingBase表单自身校验中校验流程切换submissionAttemptsBase提交尝试计数handleSubmit递增isSubmitSuccessfulBase上次提交是否成功handleSubmit维护_force_re_eval?Baseprivateoptions 变化时强制重估内部机制isFormValidatingDerived表单自身正在校验store 派生isFormValidDerivederrors.length 0store 派生errorsDerived表单级错误数组引用保持store 派生isFieldsValidatingDerived任一字段校验中somestore 派生isFieldsValidDerived所有字段有效everystore 派生isTouched/isBlurredDerived任一字段 touch/blursomestore 派生isDirty/isPristineDerived任一字段值被改 / 其反义store 派生isDefaultValueDerived所有字段等于默认值everystore 派生isValidDerivedisFieldsValid isFormValidstore 派生canSubmitDerived是否可提交含canSubmitWhenInvalidstore 派生fieldMetaDerived含派生属性的完整字段元数据fieldMetaDerived计算8. 小结FormState是 TanStack Form 状态模型的对外契约11 个泛型参数把各校验时机的返回类型贯穿到errors/errorMap中实现了端到端的类型安全BaseFormState与DerivedFormState的分层则明确了「哪些是存储、哪些是计算」而 FormApi.ts 中单一 store 内的原子派生与引用保持策略保证了 UI 消费层如canSubmit驱动提交按钮、isSubmitting驱动 loading始终拿到与基础状态一致且更新开销可控的视图。理解这张表即可在任何框架适配层中精确地消费表单状态。【免费下载链接】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),仅供参考