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

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>时,出现多处类型不匹配:

  1. 非数组对象的返回值无法赋值给Annotated<T, "$", Access>
  2. 数组处理逻辑的返回值不符合目标类型
  3. 原始值(字符串、数字等)处理也存在类型错误
    改用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 13:17:32