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

为何TypeScript递归泛型函数中嵌套属性b被推断为unknown?

TypeScript递归泛型中b属性被推断为unknown的原因分析

在以下递归泛型类型示例中,第二个createFoo调用的b属性会被TypeScript推断为unknown,导致访问foo2.b.ii时报错,我们来分析背后的原因:

type FooConfig<T> = {
  [K in keyof T]: {
    [P in keyof T[K]]: number | ((t: Foo<T>) => number);
  };
};

type Foo<T> = {
  [K in keyof T]:{
    [P in keyof T[K]]: number;
  };
};

const createFoo = <
  T,
  TFoo = Foo<T>
>(args: FooConfig<T>): TFoo =>  {
  /// code
}

const foo = createFoo({
  a: {
    i: 1,
  },
  b: {
    ii: () => 1,
  }
});

// 正常工作
console.log('@-->foo', foo.b.ii);

const foo2 = createFoo({
  a: {
    i: 1,
  },
  b: {
    ii: (f) => f.a.i += 1, // 此处f.a.i的类型推断正确
    // iii: () => 1 // 取消注释后b属性推断恢复正常
  }
});

// 存在类型错误:foo2.b被推断为unknown
console.log('@-->foo2', foo2.b.ii);

核心现象

  • 第一个createFoo调用中,b.ii未使用带递归引用的函数参数,b的类型推断完全正常,能正确识别ii键。
  • 第二个调用中,b.ii使用了参数f(递归引用createFoo的返回值Foo<T>),此时整个b对象被推断为unknown,但参数f自身的类型推断是正确的。
  • 若给b添加一个非递归引用的属性(如iii: () => 1),b.ii的类型推断会立刻恢复正常。

原因解析

这是TypeScript在处理循环泛型约束时的类型推断局限性导致的:

  1. 当b.ii的函数参数引用Foo<T>时,TypeScript需要先确定T的结构才能推断Foo<T>,但T的结构又依赖FooConfig<T>中b的定义,形成了循环依赖。
  2. 当b中只有这个递归引用的属性时,TypeScript无法打破循环,只能将b的类型退化为unknown来解决冲突。
  3. 而当b中存在非递归属性(如iii: () => 1)时,TypeScript可以通过这个属性先确定T中b的部分结构,进而完成整个循环的类型推断。

临时解决方法(库开发中不够理想)

  • 方案1:添加非递归引用属性
    通过给b增加一个不依赖递归的属性,帮助TypeScript打破推断循环:
    const foo2 = createFoo({
      a: {
        i: 1,
      },
      b: {
        ii: (f) => f.a.i += 1,
        iii: () => 1
      }
    });
    
  • 方案2:显式指定参数类型
    手动给函数参数f指定类型,跳过自动推断的循环:
    const foo2 = createFoo({
      a: {
        i: 1,
      },
      b: {
        ii: (f: { a: { i: number }}) => f.a.i += 1,
      }
    });
    

内容的提问来源于stack exchange,提问作者Lynn Dylan Hurley

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 00:10:35