为何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在处理循环泛型约束时的类型推断局限性导致的:
- 当
b.ii的函数参数引用Foo<T>时,TypeScript需要先确定T的结构才能推断Foo<T>,但T的结构又依赖FooConfig<T>中b的定义,形成了循环依赖。 - 当
b中只有这个递归引用的属性时,TypeScript无法打破循环,只能将b的类型退化为unknown来解决冲突。 - 而当
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
相关产品推荐
相关产品推荐

