In TypeScript, types and values operate in distinct namespaces. It is possible to have a type named Foo
and an unrelated value also named Foo
. The compiler does not get confused because they are different entities that exist in separate contexts:
const Foo = "abc"; // <-- value named Foo
interface Foo { x: number; } // <-- type named Foo
const x = Foo; // <-- this represents the value
// const x: "abc";
const y: Foo = { x: 123 }; // <-- this represents the type
type Z = typeof Foo; // <-- this refers to the value, while Z is a type
// type Z = "abc"
This concept might be perplexing for someone whose first language is not TypeScript, but the compiler can differentiate between names referencing types versus values based on the context. Much like how native English speakers can understand sentences like "My cousin Virginia lives in Georgia" and "My cousin Georgia lives in Virginia" by interpreting the context of person versus location.
There is no necessary connection between a type and a value with the same name.
When it comes to declarations such as interface
and type
, they define a named type while variable declarations using let
and const
define a named value. Additionally, class declarations are unique because they declare both a type and a value under the same name. The type corresponds to an instance of the class, whereas the value represents the class constructor:
class Bar { a = 1; }
const b: Bar = new Bar();
// ----> ^^^ ^^^ <---
// instance type constructor value
const baz = Bar; // <-- constructor value
// const baz: typeof Bar
Thus, although there isn't a mandatory relationship between types and values with identical names, those stemming from class
declarations will exhibit a connection where the named value serves as a constructor for the named type. Individuals often mistakenly assume this common relationship is required, leading them to write typeof X
to reference a potentially non-existent constructor for instances of X
. Hopefully, this explanation prevents you from making this error in the future.
If you aim to replicate the effects of a class
declaration without actually utilizing one, allowing people to pretend it was declared in that manner, you must establish a suitable named type (via interface
or type
) along with an appropriate named value (using let
or const
). In your scenario, it would look something like this:
declare class XFake { bar(): number }
export type XExport = XFake; // named type
export const XExport = (XReal as any) as typeof XFake // named value
Remember, you might not even require the XFake
declaration since you can directly specify the instance and constructor:
export type XExport = { bar(): number };
export const XExport = (XReal as any) as new () => XExport
This approach enhances user experience, particularly within IntelliSense, where references will mention XExport
instead of
XFake</code. This makes <code>XExport
appear more like a "regular" class:
declare const c: XExport;
// const c: XExport
const ci = new XExport;
// const ci: XExport
Playground link to code