TypeScript泛型对子节点递归时的类型错误问题排查
给对象树节点添加访问控制字段并保留TypeScript类型提示
需求说明
需要实现一个函数,给对象树的每个节点注入访问控制字段$,规则如下:
- 非数组对象:
{ field: {...subFields} }→{ field: { $: 'view', ...subFieldsAnnotated } } - 数组:
{ arr: [...elements] }→{ arr: { $: 'view', $items: [...elementsAnnotated] } }
问题场景
已定义Annotated<T, NewFieldName extends string, NewFieldValue>和Accessor<T>泛型,但实现getAccessor<T>(value: T, access: Access): Accessor<T>时,出现多处类型不匹配:
- 非数组对象的返回值无法赋值给
Annotated<T, "$", Access> - 数组处理逻辑的返回值不符合目标类型
- 原始值(字符串、数字等)处理也存在类型错误
改用any参数能正常运行,但会丢失类型提示,无法基于Accessor<T>结构安全引用字段。
解决方案
1. 正确定义类型
通过条件类型明确Accessor<T>的结构,区分原始值、数组、非数组对象三种情况:
// 定义访问权限类型 type Access = 'view' | 'edit' | 'delete'; // 非数组对象的注解结构:保留原字段 + 新增$字段 type AnnotatedObject<T extends object, AccessType extends Access> = { [K in keyof T]: Accessor<T[K]>; } & { $: AccessType }; // 数组的注解结构:$字段 + $items存储注解后的数组元素 type AnnotatedArray<T, AccessType extends Access> = { $: AccessType; $items: Accessor<T>[]; }; // 核心类型:根据输入T的类型返回对应的注解结构 type Accessor<T, AccessType extends Access = 'view'> = // 如果是数组,返回数组注解结构 T extends Array<infer U> ? AnnotatedArray<U, AccessType> // 如果是对象(非数组),返回对象注解结构 : T extends object ? AnnotatedObject<T, AccessType> // 原始值直接返回原类型 : T;
2. 实现类型安全的函数
通过类型守卫区分三种数据类型,递归处理每个节点,最后用类型断言告诉TypeScript返回值符合Accessor<T>的定义(逻辑已保证结构正确性):
function getAccessor<T>(value: T, access: Access = 'view'): Accessor<T> { // 处理原始值:null/undefined/字符串/数字等直接返回 if (value === null || typeof value !== 'object') { return value as Accessor<T>; } // 处理数组:生成带$和$items的结构 if (Array.isArray(value)) { const annotatedArray = { $: access, $items: value.map(item => getAccessor(item, access)) }; return annotatedArray as Accessor<T>; } // 处理非数组对象:遍历原字段并递归注解,再添加$字段 const annotatedObj: Record<string, unknown> = { $: access }; for (const key in value) { if (Object.prototype.hasOwnProperty.call(value, key)) { annotatedObj[key] = getAccessor((value as Record<string, unknown>)[key], access); } } return annotatedObj as Accessor<T>; }
3. 验证效果
用示例测试类型提示和结构正确性:
const original = { name: 'Alice', age: 30, posts: [ { title: 'Hello', content: 'World' }, { title: 'TypeScript', content: 'Tips' } ] }; const annotated = getAccessor(original, 'view'); // 类型提示正常工作: console.log(annotated.$); // 'view' console.log(annotated.name); // 'Alice' console.log(annotated.posts.$); // 'view' console.log(annotated.posts.$items[0].title); // 'Hello'
关键说明
之前的类型错误源于Annotated泛型没有明确区分三种数据类型的分支逻辑,导致TypeScript无法正确推导返回值类型。现在通过条件类型Accessor<T>精准定义每种输入对应的输出结构,再配合函数内的类型守卫分支处理,既保证了运行时逻辑正确,又保留了完整的类型提示。
内容的提问来源于stack exchange,提问作者calmity
相关产品推荐
相关产品推荐

