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

资讯详情

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

深入解读 TanStack Form 的 `ValidationError` 类型:为什么校验错误被设计为 `unknown`

深入解读 TanStack Form 的 `ValidationError` 类型:为什么校验错误被设计为 `unknown` 深入解读 TanStack Form 的ValidationError类型为什么校验错误被设计为unknown【免费下载链接】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导读ValidationError是 TanStack Formform-core 包中定义的一个极其简洁却又贯穿全局的类型别名它直接决定了整个表单校验体系对“错误值”的约束边界。本文以 ValidationError 类型别名文档 为核心结合 form-core 源码 与官方 React 指南深入讲解这一unknown类型的设计动机、它在字段级/表单级校验链路中的实际位置以及开发者如何利用它写出类型安全且灵活的错误处理代码。一、ValidationError的定义一行代码背后的设计哲学在 packages/form-core/src/types.ts 中该类型的完整定义只有一行export type ValidationError unknown与大多数表单库将错误类型硬编码为string | string[]不同TanStack Form 刻意把错误值放宽为 TypeScript 的顶层类型unknown。这意味着校验函数可以返回任意类型的错误值字符串、错误对象、错误码数组、Standard Schema的 issue 列表甚至undefined表示校验通过库本身不预设错误的数据结构展示层如何渲染、国际化如何处理、错误如何分组全部交由应用开发者决定对第三方校验库友好Zod、Valibot、Yup 等 schema 库产生的错误结构各不相同unknown让 TanStack Form 无需为每个生态定制类型适配。可以这样理解ValidationError是 TanStack Form 校验体系里所有错误值的“基座类型”它本身不做任何约束真正的类型安全约束发生在每个校验函数validator的签名与errorMap/errors的推导类型上。二、ValidationError在整个校验体系中的位置2.1 字段级校验校验函数返回unknown字段级校验函数FieldValidateFn定义在 packages/form-core/src/FieldApi.ts其返回类型直接对应ValidationError语义export type FieldValidateFnTParentData, TName, TData (props: { value: TData fieldApi: FieldApi... }) unknown异步版本FieldValidateAsyncFn同样如此见 FieldApi.ts。而实际执行校验的入口runValidatorFieldApi.ts会把校验函数或 Standard Schema 校验器的返回结果统一收拢runValidator...(props: { validate, value, type }): unknown { if (isStandardSchemaValidator(props.validate)) { return standardSchemaValidatorsprops.type as never } return (props.validate as FieldValidateFnany, any)(props.value) as never }从源码结构可以看出无论你用的是普通函数校验还是 Standard Schema 校验最终产出的“原始错误”raw error都落在unknown这个基座上随后由上层逻辑归一化后写入errorMap。2.2 错误存储errorMap与errors两个出口错误值产生后TanStack Form 提供两种读取方式二者均由ValidationError派生方式一errors拍平后的错误数组field.state.meta.errors会把各触发时机onMount/onChange/onBlur/onSubmit/onDynamic收集到的错误拍平成数组适合“只要有错就显示”的简单场景。方式二errorMap按触发时机分组的错误字典对应类型ValidationErrorMap定义在 packages/form-core/src/types.ts每个键对应一种校验触发时机export type ValidationErrorMap... { onMount?: TOnMountReturn onChange?: TOnChangeReturn | TOnChangeAsyncReturn onBlur?: TOnBlurReturn | TOnBlurAsyncReturn onSubmit?: TOnSubmitReturn | TOnSubmitAsyncReturn onDynamic?: TOnDynamicReturn | TOnDynamicAsyncReturn onServer?: TOnServerReturn }底层实现中异步校验结果会写入字段 meta 的errorMap[errorMapKey]并同步记录来源字段级还是表单级见 FieldApi.ts 中的field.setMeta调用以及errorSourceMaptypes.ts对错误来源的追踪。2.3 表单级错误FormValidationError与GlobalFormValidationErrorValidationError也是表单级错误的基石。在 packages/form-core/src/types.ts 中export type FormValidationErrorTFormData | ValidationError | GlobalFormValidationErrorTFormData export type GlobalFormValidationErrorTFormData { form?: ValidationError fields: PartialRecordDeepKeysTFormData, ValidationError }表单级校验可以返回一个全局错误作用于整张表单也可以返回一个按字段路径映射的错误字典如{ form: ..., fields: { age: Must be 13 or older } }。DeepKeysTFormData让字段路径具备编译期检查防止拼错字段名。三、实战基于unknown的类型安全错误处理ValidationError是unknown并不意味着使用时要到处as any。恰恰相反每个校验函数自身的返回类型会被精确推断从而在编译期锁定错误结构。3.1 不同校验函数返回不同错误结构以官方 React 指南 custom-errors.md 的示例为骨架一个字段的不同校验器可以返回字符串或对象form.Field namepassword validators{{ onChange: ({ value }) { // 返回 string 或 undefined return value.length 8 ? Too short : undefined }, onBlur: ({ value }) { // 返回对象或 undefined if (!/[A-Z]/.test(value)) { return { message: Missing uppercase, level: warning } } return undefined }, }} children{(field) { // errors 数组是 string | { message: string, level: string } | undefined 的联合类型 const error field.state.meta.errors[0] if (typeof error string) { return div classNamestring-error{error}/div } else if (error typeof error object) { return div className{error.level}{error.message}/div } return null }} /由于错误值源自unknown基座TypeScript 无法在未收窄前假定其形状开发者必须通过typeof等收窄手段处理——这反而保证了运行时的稳健性避免了“类型上说是 string运行时却是对象”的隐患。3.2 使用errorMap按触发时机精细化展示结合disableErrorFlat选项可以关闭errors的拍平行为改从errorMap按来源精确取错便于对不同校验时机做差异化 UI 处理实时校验、失焦反馈、提交错误分层展示示例见 custom-errors.md{ field.state.meta.errorMap.onChange ( div classNamereal-time-error{field.state.meta.errorMap.onChange}/div ) } { field.state.meta.errorMap.onBlur ( div classNameblur-feedback{field.state.meta.errorMap.onBlur}/div ) } { field.state.meta.errorMap.onSubmit ( div classNamesubmit-error{field.state.meta.errorMap.onSubmit}/div ) }errorMap的每个键都精确对应其校验函数的返回类型TypeScript 可以直接给出string | undefined或{ code: number, message: string } | undefined这类推导见 custom-errors.md 的完整示例把错误结构错误提前到编译期暴露。3.3 订阅与全局读取ValidationError同样体现在表单级状态订阅中。在 basic-concepts.md 可以看到通过useSelector订阅form.store的errorMap可以拿到整个表单按字段组织、按时机分组的完整错误字典const errors useSelector(form.store, (state) state.errorMap)配合动态校验onDynamic见 dynamic-validation.md和焦点管理基于errorMap.onChange定位首个错误字段见 focus-management.mdValidationError支撑起了从单个字段到整张表单的完整错误可视化链路。四、与 Standard Schema 生态的衔接ValidationError的unknown设计还让 TanStack Form 可以无缝接入 Standard Schema 生态。当校验器是符合 Standard Schema 的 schema 时runValidator会走standardSchemaValidators[props.type]分支FieldApi.ts此时错误表现为StandardSchemaV1Issue[]issue 数组其结构定义于 packages/form-core/src/standardSchemaValidator.ts。由于ValidationError是unknownZod/Valibot 等库产生的 issue 数组无需任何包装即可作为错误值流转。此外类型层面的UnwrapFieldValidateOrFntypes.ts会识别“校验函数是否来自 Standard Schema”自动把字段错误类型推导为StandardSchemaV1Issue[]让 schema 校验场景同样获得完整类型提示。五、设计取舍小结关注点ValidationError unknown带来的收益错误结构自由度字符串、对象、数组、schema issue 均可作为错误值生态兼容无需为不同校验库做类型适配层类型安全约束下放到每个校验函数签名配合errorMap/errors精确推导运行时稳健消费端必须显式收窄类型避免虚假的类型承诺同时也要注意它的代价错误展示代码需要自己处理结构判断与收窄。这正是 TanStack Form “headless无头”理念的体现——库只负责状态、触发时机与存储错误如何被消费完全交由开发者掌控。六、延伸阅读类型别名本体与周边类型packages/form-core/src/types.ts字段校验函数的定义与执行入口packages/form-core/src/FieldApi.ts字段级异步校验的 Promise 调度与errorMap写入packages/form-core/src/FieldApi.ts表单级错误结构FormValidationError/GlobalFormValidationErrorpackages/form-core/src/types.ts错误定制与类型安全实战docs/framework/react/guides/custom-errors.mdStandard Schema 类型别名docs/reference/type-aliases/StandardSchemaV1.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),仅供参考
返回列表