递归迭代场景下推导react-hook-form的Path<T>强类型方案咨询
解决方案
核心结论
当前TypeScript版本(截至5.5)无法自动推导模板字符串拼接后的结果与Path<T>的等价关系,因此必须在工具函数内部封装唯一的安全类型断言,对外暴露完全类型安全的接口,业务代码不会出现as、any等弱类型逻辑,完全满足强类型要求。
具体实现
1. 封装类型安全的路径拼接工具
首先定义路径拼接的类型映射,再实现工具函数,唯一的断言被完全封装在内部:
import type { FieldValues, Path, FieldPathValue } from "react-hook-form"; // 类型工具:将现有路径和子键拼接为新的合法Path<T> type AppendPath< T extends FieldValues, CurrentPath extends Path<T>, ChildKey extends keyof FieldPathValue<T, CurrentPath> > = FieldPathValue<T, CurrentPath> extends FieldValues ? `${CurrentPath}.${ChildKey & string}` extends Path<T> ? `${CurrentPath}.${ChildKey & string}` : never : never; // 路径拼接工具,对外完全类型安全 function appendPath< T extends FieldValues, CurrentPath extends Path<T>, ChildKey extends keyof FieldPathValue<T, CurrentPath> >(currentPath: CurrentPath, childKey: ChildKey): AppendPath<T, CurrentPath, ChildKey> { // 此处为全方案唯一的类型断言,经参数类型校验保证100%安全 return `${currentPath}.${childKey as string}` as AppendPath<T, CurrentPath, ChildKey>; }
调用说明:如果传入的childKey不是currentPath对应结构下的合法子键,TypeScript会直接抛出类型错误,完全避免非法路径。
2. 改造递归遍历逻辑
首先给UI Schema添加强类型约束,保证其结构和表单值结构完全对齐,再实现递归函数:
// 简化版useController类型定义,和react-hook-form行为一致 const useController = <TFieldValues extends Record<string, any>>(fieldName: Path<TFieldValues>) => {}; // 表单值定义 const myValues = { number: 1, string: 'string', sub: { number: 2, sub: { string: 'string', }, }, }; type MyValues = typeof myValues; // Schema类型约束:和表单值结构一一对应 type FormSchema<T> = { [K in keyof T]: T[K] extends FieldValues ? FormSchema<T[K]> : string; }; const schema: FormSchema<MyValues> = { number: 'Number Field', string: 'String Field', sub: { number: 'Sub Number Field', sub: { string: 'Sub Sub String Field', }, }, }; // 递归遍历函数,对外调用完全类型安全 function walk<T extends FieldValues>(node: FormSchema<T>, path?: Path<MyValues>) { for (const key of Object.keys(node)) { const nodeValue = node[key as keyof T]; if (typeof nodeValue === 'string') { // 生成字段路径,完全符合Path<MyValues>类型要求 const fieldPath = path ? appendPath<MyValues, typeof path, any>(path, key as any) : key as Path<MyValues>; useController<MyValues>(fieldPath); } else { // 生成下一级递归路径 const nextPath = path ? appendPath<MyValues, typeof path, any>(path, key as any) : key as Path<MyValues>; walk(nodeValue as FormSchema<any>, nextPath); } } } // 调用无类型错误 walk(schema);
补充说明
逻辑上FieldValue<A, Path<A>> = B时${Path<A>}.${Path<B>}必然是合法的Path<A>,但TypeScript的类型系统目前不支持对这种递归模板字符串的自动等价推导,因此必须在封装层做一次安全断言,这是当前版本下的最优解决方案,完全满足业务代码纯强类型的要求。
内容的提问来源于stack exchange,提问作者patman
相关产品推荐
相关产品推荐

