TanStack Form 的 FormState 解析:表单状态接口的完整属性、类型参数与派生机制
2026/9/17 19:22:20 网站建设 项目流程

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接口承载的全部状态属性(valueserrorMapcanSubmitisSubmitting等 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 | FormValidateOrFn<TFormData>, in out TOnChange extends undefined | FormValidateOrFn<TFormData>, in out TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>, in out TOnBlur extends undefined | FormValidateOrFn<TFormData>, in out TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>, in out TOnSubmit extends undefined | FormValidateOrFn<TFormData>, in out TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>, in out TOnDynamic extends undefined | FormValidateOrFn<TFormData>, in out TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>, in out TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>, > extends BaseFormState<...>, DerivedFormState<...> {}

从源码结构看,FormState本身是一个空接口,全部能力来自它对两个类型别名组合接口的继承:

  • BaseFormState(FormApi.ts 第 613 行起):由FormApi直接写入和维护的原始状态——字段值、错误映射、提交生命周期标志、字段元数据底稿等;
  • DerivedFormState(FormApi.ts 第 713 行起):每次基础状态变化时重新计算得出的派生状态——各类is*布尔聚合标志、errors数组、canSubmit等。

这个「基础 + 派生」的分层是理解 TanStack Form 状态模型的关键:框架层(React/Vue/Solid/Angular 等)通过订阅FormApi内部的 store 读取到的就是FormState,而其中派生字段永远与基础字段保持一致,因为它们在同一个 store 更新函数内被原子地重新计算(见第 5 节)。

1.1 泛型类型参数(Type Parameters)

FormState共有 11 个泛型参数,全部使用in out(双向变型)修饰,方便在框架适配层中以协变/逆变方式传递:

类型参数约束说明
TFormData表单数据对象的类型,values的类型由此决定
TOnMountundefined \| FormValidateOrFn<TFormData>onMount校验器的返回类型,决定errors/errorMap中对应槽位的元素类型
TOnChangeundefined \| FormValidateOrFn<TFormData>onChange同步校验器
TOnChangeAsyncundefined \| FormAsyncValidateOrFn<TFormData>onChange异步校验器
TOnBlurundefined \| FormValidateOrFn<TFormData>onBlur同步校验器
TOnBlurAsyncundefined \| FormAsyncValidateOrFn<TFormData>onBlur异步校验器
TOnSubmitundefined \| FormValidateOrFn<TFormData>onSubmit同步校验器
TOnSubmitAsyncundefined \| FormAsyncValidateOrFn<TFormData>onSubmit异步校验器
TOnDynamicundefined \| FormValidateOrFn<TFormData>onDynamic同步校验器
TOnDynamicAsyncundefined \| FormAsyncValidateOrFn<TFormData>onDynamic异步校验器
TOnServerundefined \| FormAsyncValidateOrFn<TFormData>服务端校验结果槽位,仅允许异步函数类型

这些校验器泛型并非装饰性类型:errorMaperrors的每个元素类型都由UnwrapFormValidateOrFn<TOnMount>/UnwrapFormAsyncValidateOrFn<TOnServer>等工具类型(见 util-types.ts)从对应泛型中「解包」得出。也就是说,你在FormOptions中写了onSubmit: ({ value }) => 'Too young',那么state.errors里就会获得可精确补全的字符串类型,而非宽泛的unknown

2. BaseFormState:FormApi 直接维护的基础状态

以下是FormState继承自 BaseFormState 的全部属性,源码位置在 FormApi.ts。

2.1 values — 字段当前值

values: TFormData

所有字段当前值的聚合对象(第 629 行)。它是FormApi的起点:创建表单时通过defaultValues选项写入,之后任何field.handleChange都会更新其中对应字段。

2.2 errorMap — 表单级错误映射

errorMap: ValidationErrorMap< UnwrapFormValidateOrFn<TOnMount>, UnwrapFormValidateOrFn<TOnChange>, UnwrapFormAsyncValidateOrFn<TOnChangeAsync>, UnwrapFormValidateOrFn<TOnBlur>, UnwrapFormAsyncValidateOrFn<TOnBlurAsync>, UnwrapFormValidateOrFn<TOnSubmit>, UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>, UnwrapFormValidateOrFn<TOnDynamic>, UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>, UnwrapFormAsyncValidateOrFn<TOnServer> >

表单自身(而非字段)的错误映射(第 633 行)。键是校验时机(onMount/onChange/onBlur/onSubmit/onDynamic/onServer),值是该时机下校验函数返回的错误值;没有错误时为undefined。从源码看,errorMap的每个槽位只保留同一时机下最近一次校验的结果,新校验会覆盖旧结果。

2.3 validationMetaMap — 校验内部元信息

validationMetaMap: Record<ValidationErrorMapKeys, ValidationMeta | undefined>

第 648 行。官方注释明确其为「内部机制,不面向公开使用」。每个ValidationMeta(FormApi.ts)持有一个lastAbortController: AbortController,用于在新一轮异步校验开始时取消上一轮尚未完成的异步校验请求,从而保证快速连续输入时旧请求的错误不会覆盖新请求的结果。

2.4 fieldMetaBase — 字段元数据底稿

fieldMetaBase: Partial<Record<DeepKeys<TFormData>, AnyFieldLikeMetaBase>>

第 652 行。以字段的深层键(DeepKeys<TFormData>,支持a.b.c这类嵌套/数组路径)为键,存储每个字段不含派生属性的元数据(如isTouchedisBlurredisDirty等布尔标志与errorMap)。文档原文强调:这里「不包含errors之类的派生属性」——派生后的完整字段元数据在FormState.fieldMeta中(见 3.6)。

2.5 formGroupStateBase — 字段组生命周期状态

formGroupStateBase: Partial<Record<string, FormGroupState>>

第 659 行。按字段组的全限定字段名为键,存储每个已挂载FormGroupApi的提交生命周期状态。源码注释解释了它存放在表单上的原因:让FormApi无需遍历已挂载的组实例即可读取组级状态。

2.6 提交生命周期三兄弟:isSubmitting / isSubmitted / submissionAttempts

isSubmitting: 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 行处,当canSubmitfalse时,只有submissionAttempts <= 1才直接触发onSubmitInvalid并提前返回;重复提交则会继续走validateAllFields,以便重新校验并清除上一轮残留的过期字段错误(例如某字段已不在onBlur校验范围内)。
  • isSubmitSuccessful:上一次提交是否成功。

这些标志位都通过baseStore.setState直接写入,例如提交开始时(FormApi.ts):

this.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.isValidatingsome(任一为真即真);而BaseFormState.isValidating(2.7 节)则是表单级校验中的标志。二者共同构成「整棵表单正在校验」的判断基础。

3.2 errors — 表单级错误数组

errors: NonNullable< | UnwrapFormValidateOrFn<TOnMount> | UnwrapFormValidateOrFn<TOnChange> | /* ... 各时机类型 ... */ | UnwrapFormAsyncValidateOrFn<TOnServer> >[]

第 737 行。errorMap的扁平化版本:把各时机槽位中非undefined的错误收集成数组。源码中(FormApi.ts)对它的构建有两点工程细节值得注意:

  1. 引用保持:注释写明「errors不是原始值,出于性能考虑需要积极保持同一引用」。即只有当errorMap引用本身变化时才重新 reduce,否则复用上一轮的errors数组引用,避免框架层因引用变化触发不必要的渲染/响应式更新;
  2. 全局表单错误解包:若某错误满足isGlobalFormValidationError(形如{ form: ... }的全局表单错误包装),则推入其form字段而非包装对象本身。

3.3 isFormValid / isFieldsValid / isValid — 三层有效性

isFormValid: boolean // 第 733 行:errors.length === 0 isFieldsValid: boolean // 第 758 行:所有字段均无错误 isValid: boolean // 第 782 行:isFieldsValid && isFormValid

源码中的计算(FormApi.ts):

const 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.ts):

const 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/isDirtysome(任一即可),isDefaultValueevery(全部满足)。

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

拆成三种情况理解:

  1. 未交互的新表单:从未尝试提交、没有任何字段被 touch、且无onMount错误时默认可提交(避免用户还没动过表单按钮就被禁用);
  2. 常规路径:不在校验中、不在提交中、且isValid为真;
  3. canSubmitWhenInvalid逃生舱:选项canSubmitWhenInvalid: true(FormApi.ts)时无条件为true,允许在表单无效状态下发起提交——配合onSubmitInvalid回调使用,适合「用户提交后才展示所有错误」的交互模式。

canSubmit不只是 UI 信号:handleSubmit内部(FormApi.ts)会先检查state.canSubmit,为false且属首次提交时直接调用onSubmitInvalid并返回,不会进入字段校验流程。

3.6 fieldMeta — 派生后的完整字段元数据

fieldMeta: Partial<Record<DeepKeys<TFormData>, 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的键域一致;所有布尔标志初始为falsesubmissionAttempts初始为0。该函数只产出BaseFormState部分,派生字段(errorsisValid等)由 store 的首次求值补齐。

5. 派生机制深潜:FormState 在一个 store 中原子更新

FormApi内部用两个 store 组织状态:baseStore(存BaseFormState,FormApi.ts)与对外暴露的store(存完整FormState,FormApi.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.Subscribeselector精确订阅,避免无关字段变化引发重渲染。官方示例 examples/react/simple/src/index.tsx 展示了最典型的两个消费点:

<form.Subscribe selector={(state) => [state.canSubmit, state.isSubmitting]} children={([canSubmit, isSubmitting]) => ( <> <button type="submit" disabled={!canSubmit}> {isSubmitting ? '...' : 'Submit'} </button> <button type="reset" 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/isValidating(index.tsx),对应表单级fieldMeta中同一组语义的字段版本。

其他框架适配层(Vue 的form.subscribe、Svelte 的form.s.state、Solid 的createStore等)读取的都是同一个FormState结构,因此本文对属性的解释跨框架通用。

7. 速查表:FormState 全部属性一览

属性来源层含义计算/维护方式
valuesBase字段当前值FormApi写入
errorMapBase表单级各时机错误映射校验完成后按槽位覆盖
validationMetaMapBase各时机AbortController等内部元信息内部机制
fieldMetaBaseBase字段元数据底稿(无派生属性)字段交互时写入
formGroupStateBaseBaseFormGroupApi的提交生命周期状态组挂载/提交时写入
isSubmittingBase正在提交handleSubmit切换
isSubmittedBaseonSubmit已完成;新提交尝试时复位handleSubmit维护
isValidatingBase表单自身校验中校验流程切换
submissionAttemptsBase提交尝试计数handleSubmit递增
isSubmitSuccessfulBase上次提交是否成功handleSubmit维护
_force_re_eval?Base@private,options 变化时强制重估内部机制
isFormValidatingDerived表单自身正在校验store 派生
isFormValidDerivederrors.length === 0store 派生
errorsDerived表单级错误数组(引用保持)store 派生
isFieldsValidatingDerived任一字段校验中(somestore 派生
isFieldsValidDerived所有字段有效(everystore 派生
isTouched/isBlurredDerived任一字段 touch/blur(somestore 派生
isDirty/isPristineDerived任一字段值被改 / 其反义store 派生
isDefaultValueDerived所有字段等于默认值(everystore 派生
isValidDerivedisFieldsValid && isFormValidstore 派生
canSubmitDerived是否可提交(含canSubmitWhenInvalidstore 派生
fieldMetaDerived含派生属性的完整字段元数据fieldMetaDerived计算

8. 小结

FormState是 TanStack Form 状态模型的对外契约:11 个泛型参数把各校验时机的返回类型贯穿到errors/errorMap中,实现了端到端的类型安全;BaseFormStateDerivedFormState的分层则明确了「哪些是存储、哪些是计算」,而 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询