十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TanStack Form 的 FieldApiOptions 详解:字段级配置的 9 个属性与 14 个类型参数全解析

TanStack Form 的 FieldApiOptions 详解:字段级配置的 9 个属性与 14 个类型参数全解析 TanStack Form 的 FieldApiOptions 详解字段级配置的 9 个属性与 14 个类型参数全解析【免费下载链接】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 官方参考文档 FieldApiOptions结合form-core包的源码实现完整拆解FieldApiOptions接口的继承结构、全部类型参数与属性语义。读完本文你将清楚每个字段选项如defaultValue、asyncDebounceMs、asyncAlways、defaultMeta、disableErrorFlat在运行时到底如何生效、各框架适配器useField/createField等如何透传这些选项以及如何在 React 等框架中写出类型安全的字段配置。一、FieldApiOptions 是什么在哪里定义FieldApiOptions是创建FieldApi表单字段 API 实例时传入的配置对象类型。它是 TanStack Form 中字段这一抽象的完整契约绑定哪个父表单、字段叫什么名字、默认值是什么、校验器与监听器如何挂载、异步校验的节流策略如何设定。该接口定义在 FieldApi.ts#L383源码声明如下节选export interface FieldApiOptions in out TParentData, in out TName extends DeepKeysTParentData, in out TData extends DeepValueTParentData, TName, in out TOnMount extends undefined | FieldValidateOrFnTParentData, TName, TData, in out TOnChange extends undefined | FieldValidateOrFnTParentData, TName, TData, // ... 省略其余校验器泛型参数 in out TFormOnServer extends undefined | FormAsyncValidateOrFnTParentData, in out TParentSubmitMeta, extends FieldLikeApiOptions..., // 基础字段选项name、defaultValue 等 form 绑定 FieldExtraOptions... // validators 与 listeners {}从源码结构看FieldApiOptions本身没有新增任何属性它是两个接口的合并FieldLikeApiOptions定义于packages/form-core/src/types.ts提供form、name、defaultValue、asyncDebounceMs、asyncAlways、defaultMeta、disableErrorFlat这 7 个属性并继续继承 FieldLikeOptionsFieldExtraOptions提供validators与listeners两个字段。FieldLike*这套接口同时服务于FieldApi与FormGroupApi字段组这正是两者共享name/form/defaultValue等基础选项的原因。所有类型参数都使用了in out双向方差修饰符从源码结构看这是为了让泛型在读选项和写选项两个方向上都不触发逆变冲突从而保证适配器如useField在透传选项时类型推断不会退化。二、类型参数逐一解读参考文档列出了 14 个类型参数它们并非都需要手动指定——绝大多数由FormApi与字段名自动推导。按职责可分为三组数据形状三参数类型参数约束含义TParentData无父表单的数据类型即FormApiTParentData的TParentDataTNameextends DeepKeysTParentData字段名。被约束为父数据类型的深层键如address.city或items.0.name拼错字段名会直接报类型错误TDataextends DeepValueTParentData, TName字段值类型由TName从TParentData中解析出来DeepKeys与DeepValue定义在 util-types.ts对应参考文档 DeepKeys 与 DeepValue。例如对type Data { user: { email: string } }TName允许user.email此时TData自动推导为string。字段级校验器泛型12 个TOn*TOnMount、TOnChange、TOnChangeAsync、TOnBlur、TOnBlurAsync、TOnSubmit、TOnSubmitAsync、TOnDynamic、TOnDynamicAsync九个参数约束为undefined | FieldValidateOrFnTParentData, TName, TData异步版本约束为undefined | FieldAsyncValidateOrFn...。关键在于FieldValidateOrFn是一个联合类型见 FieldApi.ts#L160export type FieldValidateOrFnTParentData, TName, TData | FieldValidateFnTParentData, TName, TData // (props: { value, fieldApi }) unknown | StandardSchemaV1TData, unknown // Standard Schema v1 验证器如 Zod 的 z.string().min(1)也就是说校验器既可以是接收{ value, fieldApi }的普通函数也可以是任何实现了 Standard Schema v1 协议的验证器实例如z.string().refine(...)。这些泛型的真实作用是回传当你在validators.onChange里传入z.string().min(1)后state.meta.errors的类型就能精确到具体错误类型而不是退化为unknown。表单级校验器泛型9 个TFormOn*TFormOnMount、TFormOnChange、TFormOnChangeAsync、TFormOnBlur、TFormOnBlurAsync、TFormOnSubmit、TFormOnSubmitAsync、TFormOnDynamic、TFormOnDynamicAsync、TFormOnServer约束为undefined | FormValidateOrFnTParentData或undefined | FormAsyncValidateOrFnTParentData。它们描述的是父表单在各级校验源上的类型用于让字段的校验结果与表单级校验结果共同推导出TParentSubmitMeta提交时返回给onSubmit回调的 meta 类型。注意TFormOnServer专门对应server校验源从源码注释看它面向 SSR/SSG 场景的校验不执行任何前端逻辑见 types.ts#L39-L49。三、属性详解每个选项的运行时行为参考文档列出的属性共 9 个。以下逐一说明并给出源码中实际的生效位置。属性类型必填说明formFormApiTParentData, ...是字段所属的父表单实例nameTNameDeepKeysTParentData是字段名深层键defaultValueNoInferTData否字段默认值asyncDebounceMsnumber否异步校验的默认节流时间毫秒asyncAlwaysboolean否同步校验出错时是否仍执行异步校验defaultMetaPartialFieldLikeMeta...否字段元数据的默认值对象disableErrorFlatboolean否禁用对errors的flat(1)展开validatorsFieldValidators...否各校验源的校验器集合listenersFieldListeners...否各事件的监听器集合form 与 name字段的身份证form是字段与父表单的唯一绑定name是字段在该表单数据中的深层键路径。在FieldApi构造函数中FieldApi.ts#L712-L741二者被直接提升为实例属性constructor(opts: FieldApiOptions...) { this.form opts.form this.name opts.name this.options opts // ... }后续getFieldValue、getFieldMeta、校验、提交等所有操作都通过this.formthis.name定位字段这也是框架适配器只需传入这两个参数即可完成字段注册的原因。defaultValue只在未触碰且无值时生效defaultValue的类型是NoInferTData——用NoInfer包裹是为了防止默认值参与反向推导避免它干扰TData从字段名推导出的类型。它并非无条件覆盖表单值。在构造函数的 store 初始化逻辑中FieldApi.ts#L780-L793let value this.form.getFieldValue(this.name) if ( !meta.isTouched (value as unknown) undefined this.options.defaultValue ! undefined !evaluate(value, this.options.defaultValue) ) { value this.options.defaultValue }生效条件有四字段未被触碰isTouched为false、表单中当前值为undefined、defaultValue本身不是undefined、且evaluate判定两者不等值。换言之defaultValue是兜底值而非初始强制值不会覆盖form.defaultValues中已经提供的值。这一点在 React 适配器的测试中被明确验证例如 useField.test.tsx#L1694-L1703 中should allow field-level defaultValue用例form.Field namename defaultValueaasyncDebounceMs 与 validators.*AsyncDebounceMs两级节流asyncDebounceMs是默认节流时间只有当校验器没有提供更具体的节流时间时才生效。完整的解析链在 utils.ts#L407-L458 中getValidationLogicFn内部const { asyncDebounceMs } options const { onBlurAsyncDebounceMs, onChangeAsyncDebounceMs, onDynamicAsyncDebounceMs } (options.validators || {}) const defaultDebounceMs asyncDebounceMs ?? 0 // 按校验源选择节流时间 switch (validatorCause) { case change: debounceMs onChangeAsyncDebounceMs ?? defaultDebounceMs; break case blur: debounceMs onBlurAsyncDebounceMs ?? defaultDebounceMs; break case dynamic: debounceMs onDynamicAsyncDebounceMs ?? defaultDebounceMs; break case submit: debounceMs 0; break // submit 校验始终立即执行 } if (cause submit) debounceMs 0三条规则值得注意校验器级优先validators.onChangeAsyncDebounceMs等会覆盖选项级的asyncDebounceMssubmit 永不节流无论从哪个方向推导submit源的异步校验debounceMs都强制为0保证提交时校验立即完成不设置则为 0asyncDebounceMs ?? 0即默认不节流异步校验随触发源即时运行。React 测试 useField.test.tsx#L632 用onChangeAsyncDebounceMs: 100验证了节流后的调用次数注释中明确写道withoutonChangeAsyncDebounceMsmockFn will have been called 5 times直观展示了节流对重复触发的抑制作用。asyncAlways同步出错时是否短路异步校验asyncAlwaystypes.ts#L979-L982 的 JSDoc 描述若为true即使同步校验阶段已产生错误也仍然执行异步校验。默认行为是短路。在字段异步校验入口FieldApi.ts#L1679if (hasErrored !this.options.asyncAlways) { this.getInfo().validationMetaMap[getErrorMapKey(cause)]?.lastAbortController.abort() // 直接返回已有错误不再发起异步校验 return [...this.state.meta.errors, ...groupErrors.flat()] }同样的短路逻辑同时存在于 FormGroupApi.ts#L2322 与 FormApi.ts#L1942、FormApi.ts#L2402即字段、字段组、表单三层各自持有asyncAlways选项并独立判断。适用场景例如同步校验检查必填异步校验做服务端唯一性检查——当你希望即使为空也去服务端查一次占用状态时就设置asyncAlways: true。defaultMeta预置字段元数据defaultMeta接受PartialFieldLikeMeta...用于在字段初始化时预置元数据如初始errors、isTouched等。它的合并顺序在构造函数中FieldApi.ts#L780-L783const meta this.form.getFieldMeta(this.name) ?? { ...defaultFieldMeta, // 来自 packages/form-core/src/metaHelper.ts ...opts.defaultMeta, // 用户提供的 defaultMeta 覆盖默认值 }注意优先级如果表单中已存在该字段的 metaform.getFieldMeta(this.name)非空则完全沿用已有 metadefaultMeta仅在字段首次创建时生效。基础默认值defaultFieldMeta定义在 metaHelper.ts#L11-L24export const defaultFieldMeta: AnyFieldLikeMeta { isValidating: false, isTouched: false, isBlurred: false, isDirty: false, isPristine: true, isValid: true, isDefaultValue: true, errors: [], errorMap: {}, errorSourceMap: {}, _arrayVersion: 0, _pendingValidationsCount: 0, }defaultMeta的典型用途是在服务端渲染或错误回填时让字段一挂载就携带初始错误参考文档中 React 的提交处理指南 演示了表单级 meta 回传的用法字段级同理。disableErrorFlat保留错误的原始结构disableErrorFlattypes.ts#L1011-L1014的 JSDoc 说明禁用对field.errors的flat(1)操作这在希望保留错误原始结构时有用但不建议大多数场景使用。其生效点在表单聚合字段错误的地方FormApi.ts#L1228-L1229if (!fieldInstance || !fieldInstance.options.disableErrorFlat) { fieldErrors fieldErrors.flat(1) }正常情况下字段错误会被展开一层flat(1)成为扁平数组方便 UI 直接遍历设为true后保留unknown[][]的嵌套结构从types.ts中的多处 TODO 注释看这一分支后续计划支持返回StandardSchemaV1Issue[][]以保留 Standard Schema 验证器的完整 issue 结构types.ts#L360。validators挂载在六个校验源上的校验器validators的类型是 FieldValidators参考文档见 FieldValidators源码注释完整列出了全部成员export interface FieldValidators... { onMount?: RejectPromiseValidatorTOnMount // 挂载时运行 onChange?: RejectPromiseValidatorTOnChange // 值变化时运行如 z.string().min(1) onChangeAsync?: TOnChangeAsync // 值变化时的异步校验 onChangeAsyncDebounceMs?: number // onChangeAsync 节流ms onChangeListenTo?: DeepKeysTParentData[] // 监听哪些字段变化来触发本字段的 onChange/onChangeAsync onBlur?: RejectPromiseValidatorTOnBlur // 失焦时运行 onBlurAsync?: TOnBlurAsync onBlurAsyncDebounceMs?: number onBlurListenTo?: DeepKeysTParentData[] onSubmit?: RejectPromiseValidatorTOnSubmit // 提交时运行 onSubmitAsync?: TSubmitAsync onDynamic?: RejectPromiseValidatorTOnDynamic // 动态值结构校验源 onDynamicAsync?: TOnDynamicAsync onDynamicAsyncDebounceMs?: number }各校验源与内部ValidationCausechange | blur | submit | mount | server | dynamic见 types.ts#L43一一对应。两个实用细节*ListenTo依赖监听onChangeListenTo: [lastName]表示当lastName变化时重跑本字段的onChange/onChangeAsync用于跨字段联动校验如确认密码监听密码同步拒绝 PromiseRejectPromiseValidator会在编译期禁止把返回 Promise 的函数放到同步校验位如onChange强制异步逻辑走onChangeAsync避免异步校验被当同步用这一常见坑。listeners非校验的事件钩子listeners的类型是 FieldListeners参考文档见 FieldListenersexport interface FieldListenersTParentData, TName, TData { onChange?: FieldListenerFnTParentData, TName, TData onChangeDebounceMs?: number onBlur?: FieldListenerFnTParentData, TName, TData onBlurDebounceMs?: number onMount?: FieldListenerFnTParentData, TName, TData onUnmount?: FieldListenerFnTParentData, TName, TData onSubmit?: FieldListenerFnTParentData, TName, TData onGroupSubmit?: FieldListenerFnTParentData, TName, TData }与validators的区别在于listeners的回调签名是(props: { value, fieldApi }) void不产生错误只用于副作用记录日志、同步外部状态等且onChange/onBlur支持独立的*DebounceMs节流。onUnmount与onGroupSubmit所属字段组提交时触发是validators所没有的时机。四、实战框架适配器如何使用 FieldApiOptions文档说明通常不需要直接new FieldApi而是通过框架适配器创建。FieldApiOptions正是这些适配器的公共底座——React 的useFielduseField.tsx、Solid 的createField、Svelte 的Field.svelte等最终都会把 props 组装成一个FieldApiOptions传入构造函数。以官方示例 examples/react/simple/src/index.tsx 为例把上面所有选项串起来import { useForm } from tanstack/react-form function App() { const form useForm({ defaultValues: { firstName: }, onSubmit: async ({ value }) console.log(value), }) return ( form.Field namefirstName // name: DeepKeysFormData defaultValueTess // defaultValue: NoInferTData asyncDebounceMs{300} // 异步校验默认节流 300ms validators{{ onChange: ({ value }) !value ? A first name is required : value.length 3 ? First name must be at least 3 characters : undefined, onChangeAsyncDebounceMs: 500, // 覆盖 asyncDebounceMs onChangeAsync: async ({ value }) { await new Promise((r) setTimeout(r, 1000)) return value.includes(error) No error allowed in first name }, }} listeners{{ onChange: ({ value }) console.log(typed:, value), }} {(field) ( input id{field.name} name{field.name} value{field.state.value} onChange{(e) field.handleChange(e.target.value)} onBlur{field.handleBlur} / {field.state.meta.isValidating emValidating.../em} {field.state.meta.errors.map((e) em key{String(e)}{e}/em)} / )} /form.Field ) }字段运行时状态field.state.value与field.state.meta由构造函数内的createStore驱动FieldApi.ts#L749store 每次重算都会先读取this.form.store触发对表单状态的订阅再合并defaultFieldMeta与opts.defaultMeta得出当前 meta实现了选项到响应式状态的单向数据流。五、核心测试覆盖FieldApiOptions各属性在核心测试中均有对应验证可作为行为基准字段级defaultValueuseField.test.tsx#L1694should allow field-level defaultValue与defaultValue不触发渲染期setState警告的用例同文件 L1647异步节流useField.test.tsx#L632 的onChangeAsyncDebounceMs: 100断言调用次数字段 API 行为总集FieldApi.spec.ts类型层面含FieldApiOptions泛型推导FieldApi.test-d.ts 及 React 侧的 useField.test-d.tsx。六、小结与相关文档FieldApiOptions是 TanStack Form 字段体系的配置总纲formname完成字段注册defaultValue/defaultMeta控制初始状态validators/listeners分别在六个校验源与四个事件时机上挂载逻辑asyncDebounceMs/asyncAlways/disableErrorFlat三个开关精细调节异步校验与错误结构的行为。由于接口继承自FieldLikeApiOptions本文描述的语义同样适用于FormGroupApi字段组的同名选项。延伸阅读均为仓库内文档FieldApi 类参考FieldValidators 接口FieldListeners 接口FormApi 类参考FormGroupOptions 接口React 字段指南 与 安装指南【免费下载链接】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),仅供参考
返回列表