TypeScript中省略泛型类型为何为对象?相关疑问咨询
Great question! Let's break this down into two clear parts: why TypeScript defaults unspecified generics to {} instead of any, and how to enforce explicit generic type arguments for your classes.
Why the default is {} instead of any
First, it’s critical to distinguish {} from any—they behave very differently:
anycompletely opts out of TypeScript’s type checking. You can call arbitrary methods, assign values of any type, and access non-existent properties without compile-time warnings.{}is the broadest object type available. It accepts any value that is (or can be coerced to) an object, but still enforces basic type safety—for example, you’ll get a warning if you try to call a method that doesn’t exist on{}.
The TypeScript team chose {} as the default for unbound generics for three key reasons:
- Prioritize type safety by default: They wanted to avoid implicitly enabling the "no checks" behavior of
anyunless developers explicitly ask for it.{}keeps some guardrails in place while remaining flexible for untyped use cases. - Backward compatibility: Early versions of TypeScript used
{}as the default for unspecified generics. Changing this toanywould break existing codebases that rely on this behavior. - Philosophical alignment: TypeScript’s core goal is to add static typing to JavaScript without getting in the way.
{}strikes a balance—it’s permissive enough to work with most unplanned use cases, but still catches obvious mistakes thatanywould ignore.
Your guess about "T belonging to class object types" is partially correct, but this behavior applies to all generics (not just classes). For example, a generic function like function getValue<T>(): T { return {} as T; } will return a value of type {} when called without a type argument, not any.
How to enforce explicit generic type arguments
If you want to ensure developers can’t instantiate your generic class without specifying (or letting TypeScript infer) a type for T, here are two reliable approaches:
1. Add a constructor parameter of type T
The simplest method is to require a value of type T in the constructor. This forces TypeScript to either infer T from the argument or throw an error if no argument is provided (since it can’t guess T). Example:
class Foo<T> { constructor(private value: T) {} } // ❌ Error: Unable to infer type argument(s) for Foo<T> const badFoo = new Foo(); // ✅ OK: T is inferred as string from the argument const goodFoo1 = new Foo("hello"); // ✅ OK: Explicitly specify T as number const goodFoo2 = new Foo<number>(42);
2. Use a compile-time check for the default {} type
If your class doesn’t need a constructor parameter, you can add a hacky but effective compile-time check to block instantiation with the default {} type. This uses conditional types to throw an error when T is the default:
class Foo<T> { constructor() { // If T is the default {}, this will fail to compile type MustSpecifyType = T extends {} ? never : T; // @ts-expect-error: Forces error when T is {} const _check: MustSpecifyType = {} as any; } } // ❌ Error: Type '{}' is not assignable to type 'never' const badFoo = new Foo(); // ✅ OK: Explicitly specify T const goodFoo = new Foo<string>();
Note that there’s no built-in TypeScript compiler option to enforce explicit generic types globally—you’ll need to implement these checks per class.
内容的提问来源于stack exchange,提问作者Domske

