TanStack Form 中的 isStandardSchemaValidator:Standard Schema 校验器的运行时识别机制
TanStack Form 中的 isStandardSchemaValidatorStandard Schema 校验器的运行时识别机制【免费下载链接】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本文围绕 isStandardSchemaValidator 这一类型守卫函数展开讲解 TanStack Formform-core 包如何在运行时识别 Zod、Valibot、ArkType 等 Standard Schema v1 校验器并将其无缝接入FieldApi/FormApi/FormGroupApi的校验管线。读完本文你将理解~standard品牌属性如何充当“校验器指纹”、同步/异步两条校验路径的差异、表单级与字段级错误结果的结构区别以及如何在项目里直接把 schema 对象传给validators配置项。函数签名与定义根据 API 参考文档 isStandardSchemaValidator该函数签名为function isStandardSchemaValidator(validator): validator is StandardSchemaV1unknown, unknown;参数validator: unknown—— 传入任意值通常是validators配置中的onChange、onBlur等回调位置的实参。返回值类型谓词validator is StandardSchemaV1unknown, unknown—— 判断为真时TypeScript 会把validator收窄为StandardSchemaV1类型。源码实现非常简短位于 packages/form-core/src/standardSchemaValidator.tsexport const isStandardSchemaValidator ( validator: unknown, ): validator is StandardSchemaV1 !!validator ~standard in (validator as object)其核心逻辑是一次鸭子类型duck typing探测只要传入的值是非空对象且带有以~standard为键的属性就认为它遵循 Standard Schema v1 规范。这里的~standard是 Standard Schema 规范约定在 schema 实例上的“品牌属性”brand property也是规范中识别一个对象是否为 schema 的官方方式。选择属性探测而不是构造函数 instanceof 判断使得任何库——无论 Zod、Valibot 还是 ArkType——只要实现了该约定都能被同一个守卫识别无需 TanStack Form 对每个库做特判。StandardSchemaV1 类型结构isStandardSchemaValidator的判定目标类型StandardSchemaV1定义在同一个文件中standardSchemaValidator.ts另见 API 参考 StandardSchemaV1export type StandardSchemaV1Input unknown, Output Input { readonly ~standard: StandardSchemaV1PropsInput, Output } interface StandardSchemaV1PropsInput unknown, Output Input { readonly version: 1 // 规范版本号固定为 1 readonly vendor: string // 库厂商名如 Zod readonly validate: (value: unknown) | StandardSchemaV1ResultOutput | PromiseStandardSchemaV1ResultOutput readonly types?: StandardSchemaV1TypesInput, Output }几个关键点validate的返回值可以是StandardSchemaV1Result或PromiseStandardSchemaV1Result即规范本身允许同步和异步两种 schema校验成功时结果携带value推断后的输出值失败时携带issues数组每个 issue 的结构为 StandardSchemaV1Issueexport interface StandardSchemaV1Issue { readonly message: string readonly path?: ReadonlyArrayPropertyKey | StandardSchemaV1PathSegment }issue.path描述了错误在数据中的位置这是后面“表单级错误按路径分发到各字段”的基础。在 FormCore 校验管线中的三个调用点isStandardSchemaValidator不是孤立的工具函数它是runValidator私有方法的分支开关。在 core 包中共有三处调用分别覆盖字段、表单组、表单三个层级的校验执行入口字段级—— FieldApi.runValidator第 845 行if (isStandardSchemaValidator(props.validate)) { return standardSchemaValidatorsprops.type as never } return (props.validate as FieldValidateFnany, any)(props.value) as never表单组级—— FormGroupApi.runValidator第 1423 行逻辑相同但多了一步结果重映射见下文“分组校验的特殊处理”表单级—— FormApi.ts第 1648 行与字段级类似。三个调用点的模式完全一致先用isStandardSchemaValidator判别传入的 validator 是不是 schema 对象是则委托给 standardSchemaValidators 适配器执行否则按普通校验函数调用。props.type取值为validate | validateAsync对应同步/异步两条路径。从源码结构看这就是 TanStack Form 对外宣称“原生支持 Standard Schema 库”的全部识别机制没有任何instanceof或库名白名单只有一个in运算符。standardSchemaValidators守卫命中后的执行适配器当守卫返回true时实际执行交给同文件导出的standardSchemaValidators对象standardSchemaValidator.ts它提供两个方法export const standardSchemaValidators { // 同步 validate({ value, validationSource }, schema) { const result schema[~standard].validate(value) if (result instanceof Promise) { throw new Error(async function passed to sync validator) } if (!result.issues) return // 校验通过 if (validationSource field) { return result.issues // 字段级直接返回 issue 数组 } return transformFormIssues(result.issues, value) // 表单级按路径分发 }, // 异步 async validateAsync({ value, validationSource }, schema) { const result await schema[~standard].validate(value) if (!result.issues) return if (validationSource field) return result.issues return transformFormIssues(result.issues, value) }, }值得注意的细节同步路径会主动拦截异步 schemavalidate中如果~standard.validate(value)返回了Promise直接抛出async function passed to sync validator。这提示开发者如果 schema 含异步逻辑如 Zod 的refine(async ...)应把它配置在onChangeAsync等异步校验槽位而不是onChange。两种validationSource对应两种结果形状由 TStandardSchemaValidatorIssue 表达type TStandardSchemaValidatorIssueTSource extends ValidationSource TSource extends form ? { form: Recordstring, StandardSchemaV1Issue[], fields: Recordstring, StandardSchemaV1Issue[] } : TSource extends field ? StandardSchemaV1Issue[] : never字段级校验直接返回StandardSchemaV1Issue[]表单级校验返回{ form, fields }两个记录键为表单字段的深路径。表单级 issue 的路径前缀化表单级转换依赖prefixSchemaToErrorsstandardSchemaValidator.ts它遍历每个 issue 的path沿着当前表单值逐级取值把路径段拼成a.b.0.c这样的深路径键。其中对数组下标有专门处理——源码注释指出 Standard Schema 未规定数组访问用数字还是字符串化的数字因此只要沿路径走到的当前值是数组且路径段可转成数字就按[0]、[1]的形式拼接否则按点号拼接。测试文件 standardSchemaValidator.spec.ts 中有大量针对此逻辑的用例如should handle numeric array indices correctly、should handle string array indices from standard schema validators、should handle nested arrays with mixed numeric and string indices等覆盖了数字下标、字符串下标、嵌套混合下标以及数字型对象键等边界情况。最终transformFormIssues会把同一份路径化结果同时填入form和fields两个键即表单级校验的错误既能以完整路径挂在表单上也能按字段名分发供各FieldApi实例消费。分组校验的特殊处理FormGroupApi.runValidator在守卫命中后还多了一层语义修正FormGroupApi.tsStandard Schema 在validationSource: form时返回{ form, fields }但对 group 而言form键具有误导性因此同步路径直接调用remapStandardSchemaResultForGroup(result)异步路径则在 Promise 链上.then(remapStandardSchemaResultForGroup)把结果重映射为{ group, fields }形状。这也解释了为什么手动函数形式的 group 校验器被约定返回{ group, fields }——两种写法在此归一。实际使用把 schema 直接传进 validators理解了识别机制后API 使用方式非常直接凡是可以传“校验函数”的位置onChange、onBlur、onSubmit等都可以直接传一个 Standard Schema 对象例如参考 React 校验指南 的 Standard Schema Libraries 一节const userSchema z.object({ age: z.number().gte(13, You must be 13 to make an account), }) function App() { const form useForm({ defaultValues: { age: 0 }, validators: { onChange: userSchema, // 直接传 schema无需包装成函数 }, }) return ( div form.Field nameage children{(field) { return {/* ... */}/ }} / /div ) }异步场景同理把含async refine的 schema 放在onChangeAsync并配合onChangeAsyncDebounceMs防抖form.Field nameage validators{{ onChange: z.number().gte(13, You must be 13 to make an account), onChangeAsyncDebounceMs: 500, onChangeAsync: z.number().refine( async (value) { const currentAge await fetchCurrentAgeOnProfile() return value currentAge }, { message: You can only increase the age }, ), }} children{(field) {/* ... */}/} /文档中同时给出两类提示一是务必使用支持 Standard Schema 的最新版 schema 库旧版本可能没有~standard品牌属性此时isStandardSchemaValidator会判定为falseschema 会被当作普通函数调用而失效二是校验不会提供转换后的值transformed values该能力不在 Standard Schema 校验路径的职责内。若需要更细粒度的控制指南还演示了“schema 回调”组合在异步回调中调用fieldApi.parseValueWithSchema(schema)拿到该 schema 的 issue再继续后续校验逻辑。parseValueWithSchema的底层就是 FieldApi.ts 中直接转发给standardSchemaValidators.validate/validateAsync。测试验证packages/form-core/tests/standardSchemaValidator.spec.ts 中的用例印证了上述机制的覆盖面should detect a sync standard schema validator even without a validator adapter/should detect an async standard schema validator ...——验证isStandardSchemaValidator本身对同步、异步 schema 的识别should support standard schema sync validation with zod / valibot / arktype——验证守卫命中后与三家典型库的集成should handle form-level field errors for fields without a mounted FieldApi instance——验证表单级错误按路径分发后未挂载字段实例也能收到错误数组下标系列用例——验证prefixSchemaToErrors的路径拼接。小结isStandardSchemaValidator是 TanStack Form 接入 Standard Schema 生态的“守门人”它用一行!!validator ~standard in validator完成对任意标准 schema 库的运行时识别再由standardSchemaValidators适配器统一执行同步/异步校验、区分字段级与表单级分组级的结果形状并把 issue 路径前缀化为可分发的深路径键。理解这条链路后开发者既可以直接把 Zod/Valibot/ArkType 等 schema 传入validators各槽位也能借助parseValueWithSchema在自定义回调中复用标准 schema 的校验能力而无需了解各库的差异细节。【免费下载链接】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),仅供参考