开启strictFunctionTypes=true时,如何避免TypeScript对泛型类实例的类型推断过窄?
Great question! The issue here boils down to how TypeScript infers generic types when strictFunctionTypes is enabled—since your Foo class has a function parameter that uses Foo<T> in a contravariant position (function arguments are contravariant under strict mode), TypeScript locks in the literal type 5 for T instead of widening it to number.
Let's look at some clean, maintainable solutions that keep strictFunctionTypes enabled without needing explicit generic type annotations every time:
Solution 1: Reorder Constructor Parameters
TypeScript infers generic types based on the order of arguments. By moving the fn parameter first, TypeScript will prioritize inferring T from the function's type annotation (Foo<number>) instead of the literal 5:
class Foo<T> { constructor( public fn: (value: Foo<T>) => void, public value: T ) {} } // Now T is inferred as number automatically const foo = new Foo((v: Foo<number>) => {}, 5); foo.value = 100; // No error!
Solution 2: Use a Static Factory Method
If reordering constructor parameters doesn't fit your API design, a static factory method can guide type inference the same way, while keeping your constructor parameter order intact:
class Foo<T> { constructor(public value: T, public fn: (value: Foo<T>) => void) {} // Factory method prioritizes inferring T from the fn parameter static create<T>(fn: (value: Foo<T>) => void, value: T): Foo<T> { return new Foo(value, fn); } } const foo = Foo.create((v: Foo<number>) => {}, 5); foo.value = 100; // Works perfectly
Solution 3: Widen the Literal Type (Lightweight Annotation)
If you prefer to keep the original constructor order, you can use a type assertion to widen the literal 5 to number—this is a minimal annotation that's less verbose than explicitly specifying Foo<number>:
class Foo<T> { constructor(public value: T, public fn: (value: Foo<T>) => void) {} } const foo = new Foo(5 as number, (v: Foo<number>) => {}); foo.value = 100; // No error
Why These Work
When strictFunctionTypes is enabled, TypeScript enforces stricter contravariance for function parameters. By guiding TypeScript to infer T from the fn parameter (which explicitly uses Foo<number>) instead of the literal value, we ensure T is widened to number rather than being locked to the literal 5.
内容的提问来源于stack exchange,提问作者Mike Jerred

