TanStack Form Solid 动态校验(Dynamic Validation)完整指南:用 `revalidateLogic` 与 `onDynamic` 实现随表单状态变化的校验规则
2026/9/17 6:20:20 网站建设 项目流程

TanStack Form Solid 动态校验(Dynamic Validation)完整指南:用revalidateLogiconDynamic实现随表单状态变化的校验规则

【免费下载链接】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(Solid 适配器)通过onDynamic校验函数与revalidateLogic校验逻辑,为这类"随表单状态动态变化"的校验场景提供了内建支持。阅读完本文,你将掌握如何在 Solid 项目中配置revalidateLogic的提交前后两种校验模式、如何在表单级与字段级使用onDynamic、如何读取errorMap中的动态错误,以及如何将onDynamicAsync防抖和 Zod/Valibot 标准 schema 校验接入其中,并深入理解其底层实现原理。

什么是动态校验(Dynamic Validation)

常规的表单校验(onChangeonBluronSubmit)在表单生命周期内的触发时机是固定不变的。而动态校验(Dynamic Validation)解决的核心问题是:校验规则和校验触发时机本身需要随表单当前状态而变化

最常见的应用场景是"首次提交前 vs 首次提交后":

  • 提交前:不打扰用户,只在提交时统一校验;
  • 提交后:用户已经知道规则,此时改为在每次输入变化时(change)或失焦时(blur)实时校验,让用户能立刻看到错误并修正。

TanStack Form 通过onDynamic校验函数支持这种能力。该函数接收与其它校验函数相同的参数(如value),但其触发时机由表单的validationLogic决定。文档 docs/framework/solid/guides/dynamic-validation.md 给出的最小示例如下:

import { revalidateLogic, createForm } from '@tanstack/solid-form' // ... const form = createForm(() => ({ defaultValues: { firstName: '', lastName: '', }, // If this is omitted, onDynamic will not be called validationLogic: revalidateLogic(), validators: { onDynamic: ({ value }) => { if (!value.firstName) { return { firstName: 'A first name is required' } } return undefined }, }, }))

关键点:默认情况下onDynamic不会被调用,必须将revalidateLogic()传入createFormvalidationLogic选项,动态校验才会生效。

为什么默认不调用:validationLogic的职责

在 packages/form-core/src/ValidationLogic.ts 中可以看到,ValidationLogicFn是一个纯函数,其职责是决定在某类校验事件发生时应该执行哪些校验器(validators)。它的签名如下:

export type ValidationLogicFn = (props: ValidationLogicProps) => void

ValidationLogicProps中除了formvalidatorseventblur | change | submit | mount | serverasync标记)之外,还包含一个runValidation回调。默认的defaultValidationLogic(同文件 packages/form-core/src/ValidationLogic.ts)只会调度onMountonChangeonBluronSubmitonServer这些校验器,完全不涉及onDynamic——这正是文档强调"不传revalidateLogiconDynamic不会被调用"的源码依据。

revalidateLogic:提交前后的双重校验模式

revalidateLogic允许你分别指定首次提交前首次提交后两种校验触发模式,从而实现动态地切换校验规则的行为。它接收两个参数(见 packages/form-core/src/ValidationLogic.ts 中RevalidateLogicProps的定义):

参数含义可选值默认值
mode首次表单提交之前的校验模式'change'/'blur'/'submit''submit'
modeAfterSubmission表单提交之后的校验模式'change'/'blur'/'submit''change'

各模式含义:

  • change:每次值变化时校验;
  • blur:字段失焦时校验;
  • submit:提交时校验。

例如,希望"提交前只在提交时校验,提交后在失焦时重新校验",可以这样配置:

const form = createForm(() => ({ // ... validationLogic: revalidateLogic({ mode: 'submit', modeAfterSubmission: 'blur', }), // ... }))

底层实现:submissionAttempts决定当前模式

从 packages/form-core/src/ValidationLogic.ts 的实现可以看出,revalidateLogic并不是简单比较"是否已提交",而是读取表单状态的提交次数:

const submissionAttempts = props.group ? props.group.state.meta.submissionAttempts : props.form.state.submissionAttempts const modeToWatch = submissionAttempts === 0 ? mode : modeAfterSubmission if ([modeToWatch, 'submit'].includes(props.event.type)) { validatorsToAdd.push(dynamicValidator) }
  • submissionAttempts === 0时,使用mode作为生效模式;
  • 一旦发生过提交(submissionAttempts > 0),立即切换为modeAfterSubmission
  • 无论哪种模式,submit事件始终会触发onDynamic[modeToWatch, 'submit'].includes(event.type)),保证每次提交时动态校验一定执行。

此外,revalidateLogic在追加onDynamic校验器的同时,还会先通过defaultValidationLogic收集表单原有的默认校验器(如onChange等),最终以[...defaultValidators, ...validatorsToAdd]一并执行。这意味着它不会替代你已有的其它校验,而是叠加在其上。

源码中的两种典型配置验证

packages/form-core/tests/DynamicValidation.spec.ts 中通过两个测试完整验证了上述行为:

  • 'rhf validation should work as-expected':使用默认配置revalidateLogic()mode: 'submit'modeAfterSubmission: 'change')。挂载后修改值不产生onDynamic错误;handleSubmit()后立即出现错误;再次修改为合法值后错误自动清除——证明"提交前只在校验,提交后在 change 时实时校验"。
  • 'rhf validation should handle mode and reValidateMode':使用revalidateLogic({ mode: 'change', modeAfterSubmission: 'blur' })。此时提交前改值就会触发校验;提交后改值不再触发,而handleBlur()才清除错误。

访问动态校验错误:form.state.errorMap

onChangeonBlur校验一样,onDynamic校验产生的错误通过form.state.errorMap对象读取。因为onDynamic的校验源(cause)被标记为'dynamic',其错误存放在errorMap.onDynamic键下。从 packages/form-core/src/FormApi.ts 的getErrorMapKey映射可以看到:

case 'dynamic': return 'onDynamic'

在 Solid 组件中读取方式如下:

function App() { const form = createForm(() => ({ // ... validationLogic: revalidateLogic(), validators: { onDynamic: ({ value }) => { if (!value.firstName) { return { firstName: 'A first name is required' } } return undefined }, }, })) return <p>{form.state.errorMap.onDynamic?.firstName}</p> }

错误值类型取决于校验器返回值:表单级校验返回的是字段名到错误消息的对象,因此这里通过?.firstName取对应字段的错误。字段级校验返回字符串时,对应错误则是字符串。

与其他校验逻辑组合使用

onDynamic可以和平常的onChangeonBlur等校验共存。此时它们各自产生的错误分别存放在errorMap的不同键下,互不干扰:

import { revalidateLogic, createForm } from '@tanstack/solid-form' function App() { const form = createForm(() => ({ defaultValues: { firstName: '', lastName: '', }, validationLogic: revalidateLogic(), validators: { onChange: ({ value }) => { if (!value.firstName) { return { firstName: 'A first name is required' } } return undefined }, onDynamic: ({ value }) => { if (!value.lastName) { return { lastName: 'A last name is required' } } return undefined }, }, })) return ( <div> <p>{form.state.errorMap.onChange?.firstName}</p> <p>{form.state.errorMap.onDynamic?.lastName}</p> </div> ) }

与字段(Field)配合使用

onDynamic同样适用于字段级校验,用法与其它字段校验一致:将校验器写在form.Fieldvalidators中,错误通过field().state.meta.errorMap.onDynamic读取:

function App() { const form = createForm(() => ({ defaultValues: { name: '', age: 0, }, validationLogic: revalidateLogic(), onSubmit({ value }) { alert(JSON.stringify(value)) }, })) return ( <form onSubmit={(e) => { e.preventDefault() e.stopPropagation() form.handleSubmit() }} > <form.Field name={'age'} validators={{ onDynamic: ({ value }) => value > 18 ? undefined : 'Age must be greater than 18', }} children={(field) => ( <div> <input type="number" onInput={(e) => field().handleChange(e.target.valueAsNumber)} onBlur={field().handleBlur} value={field().state.value} /> <p style={{ color: 'red' }}> {field().state.meta.errorMap.onDynamic} </p> </div> )} /> <button type="submit">Submit</button> </form> ) }

字段级onDynamic的触发同样受表单级validationLogic: revalidateLogic()控制:提交前按mode(默认submit)触发,提交后按modeAfterSubmission(默认change)触发。这在 packages/form-core/tests/DynamicValidation.spec.ts 的'rhf validation should work for fields as well'测试中被验证:字段改值后提交前无错误,提交后立即出现错误,改回合法值后错误清除。

与 FormGroup 的组合说明

revalidateLogic还感知FormGroupApi:当校验器属于某个表单分组(group)时,它读取的是该分组自身state.meta.submissionAttempts来决定切换时机,而不是父表单的提交次数(见 packages/form-core/src/ValidationLogic.ts)。这保证多步骤/分组表单中,只有当某个分组自己提交过后,其onDynamic才会切换到提交后模式。

异步动态校验与防抖

onDynamic同样支持异步版本onDynamicAsync,并可配合onDynamicAsyncDebounceMs设置防抖毫秒数,避免高频输入导致过多的异步校验请求:

const form = createForm(() => ({ defaultValues: { username: '', }, validationLogic: revalidateLogic(), validators: { onDynamicAsyncDebounceMs: 500, // Debounce the async validation by 500ms onDynamicAsync: async ({ value }) => { if (!value.username) { return { username: 'Username is required' } } // Simulate an async validation const isValid = await validateUsername(value.username) return isValid ? undefined : { username: 'Username is already taken' } }, }, }))

异步校验的底层调度在 packages/form-core/src/ValidationLogic.ts 中可见:revalidateLogic会根据事件是否异步(props.event.async)自动选择onDynamicAsynconDynamic,因此同一套校验逻辑函数体即可同时服务同步与异步场景。异步版本的错误同样落在errorMap.onDynamic键下,读取方式完全一致。

packages/form-core/tests/DynamicValidation.spec.ts 中的'rhf validation should work as-expected with async validators'测试证实:使用onDynamicAsync时,提交前不产生错误,handleSubmit()且异步校验 resolve 后错误才出现在form.state.errorMap.onDynamic

使用标准 Schema(Zod / Valibot)进行动态校验

onDynamic校验器同样接受标准 schema 校验库(如 Valibot、Zod),从而可以用声明式的方式定义复杂、可随表单状态动态变化的校验规则。以 Zod 为例,直接将 schema 对象赋给onDynamic即可:

import { z } from 'zod' const schema = z.object({ firstName: z.string().min(1, 'A first name is required'), lastName: z.string().min(1, 'A last name is required'), }) const form = createForm(() => ({ defaultValues: { firstName: '', lastName: '', }, validationLogic: revalidateLogic(), validators: { onDynamic: schema, }, }))

该能力的底层由 packages/form-core/src/standardSchemaValidator.ts 提供:TanStack Form 在运行任何校验器之前,会先通过standardSchemaValidators检测校验器是否为标准 schema(实现了标准 schema v1 接口的对象),若是则将其转换为普通校验函数执行。因此onDynamic: schema与手写onDynamic: ({ value }) => ...在触发时机上完全一致,只是规则定义方式更声明化。

总结:动态校验的完整工作流

回顾整个机制,TanStack Form(Solid)的动态校验链路如下:

  1. createForm选项中传入validationLogic: revalidateLogic({ mode, modeAfterSubmission }),声明提交前/提交后的校验触发模式(默认'submit''change');
  2. 在表单级validators或字段级validators中定义onDynamic(或onDynamicAsync/onDynamicAsyncDebounceMs/ 标准 schema)校验器;
  3. 引擎在每次change/blur/submit事件时调用validationLogic,由 packages/form-core/src/ValidationLogic.ts 依据submissionAttempts判断当前模式,决定是否将onDynamic校验器加入本轮执行列表;
  4. 校验结果按 cause'dynamic'映射到errorMap.onDynamic(表单级经 packages/form-core/src/FormApi.ts 的getErrorMapKey,字段级经 packages/form-core/src/FieldApi.ts),组件中直接读取即可渲染。

这种"校验时机本身也是可配置状态"的设计,让开发者可以用极少的代码实现 React Hook Form 风格的动态重校验体验,同时保持 TanStack Form 一贯的类型安全与框架无关性(revalidateLogic定义在 packages/form-core/src/ValidationLogic.ts,可被 React、Vue、Angular、Solid、Lit 等所有适配器复用)。

【免费下载链接】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),仅供参考

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

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

立即咨询