为何包含特定属性时TypeScript泛型类型推断结果不同?
类型约束问题:用属性返回类型约束另一属性的参数类型
问题代码
// 基础类型及继承它的另一个类型 interface Base { x: string; } interface Foo extends Base { y: number; } // 接收Foo作为参数的函数类型 type Fn<T> = (t: T) => void declare const FooFn: Fn<Foo> // 我希望利用'x'的返回类型逆变地约束'y'可分配的函数类型 type Baz = <R extends Base, F extends Fn<R>>(arg: { x: (bar: Foo) => R y?: F[] }) => R; // 注意:当尝试设置'y'时,FooFn会触发错误 // 尽管从'x'中能正确推断出R(见R1) declare const baz: Baz; const R1 = baz({ x: (ctx) => ctx }) const R2 = baz({ x: (ctx) => ctx, y: [FooFn] })
问题描述
我想要创建一个接收props对象的函数,其中x属性的返回类型要作为y属性参数的约束。但实际使用时,添加y属性会导致x的返回类型推断结果从Foo变为Base,进而引发FooFn无法赋值给y的错误。即使使用NoInfer修饰F也无法解决,我的目标是让y能接受处理x返回类型的函数数组。
原因分析
当前泛型定义<R extends Base, F extends Fn<R>>会让TypeScript同时从x和y两个属性推断R的类型。由于函数参数是逆变的,Fn<Foo>属于Fn<Base>的子类型,TypeScript会优先选择满足所有约束的最宽泛类型Base,而非从x本身推断出的Foo。这就导致x的返回类型被强制放宽,最终引发类型不匹配。
解决方案
需要让TypeScript先从x推断出R的类型,再用该类型约束y,可以去掉多余的泛型参数F,直接将y的类型与R关联:
interface Base { x: string; } interface Foo extends Base { y: number; } type Fn<T> = (t: T) => void declare const FooFn: Fn<Foo> // 修改后的Baz类型:先推断R,再约束y的函数类型 type Baz = <R extends Base>(arg: { x: (bar: Foo) => R, y?: Fn<R>[] }) => R; declare const baz: Baz; const R1 = baz({ x: (ctx) => ctx }) // R1 类型为 Foo const R2 = baz({ x: (ctx) => ctx, y: [FooFn] }) // 类型校验通过,R2 类型为 Foo
如果需要更严格的推断(防止R被反向放宽),可在TypeScript 5.4及以上版本中使用NoInfer确保y的类型不影响R的推断:
type Baz = <R extends Base>(arg: { x: (bar: Foo) => R, y?: Fn<NoInfer<R>>[] }) => R;
这样修改后,TypeScript会优先基于x的返回值确定R,再用该类型校验y中的函数,既满足了约束要求,又保证了推断的准确性。
内容的提问来源于stack exchange,提问作者Sebastian Nemeth
相关产品推荐
相关产品推荐

