You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

递归迭代场景下推导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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 00:15:10