I have the following setup in TypeScript:
// complex.ts
export type ComplexInput = { real: number, imag: number }
export class Complex {
constructor(value: ComplexInput) {
// …
}
}
// index.ts
import { Complex, type ComplexInput } from './complex.ts'
const input: ComplexInput = {
real: 3,
imag: -2,
}
const complex = new Complex(input)
This works fine, but I want to define the ComplexInput
type inside the class so that it's scoped to the class and can be referenced as something like Complex.Input
. Ideally, I would like to achieve something like this:
// complex.ts
export class Complex {
type Input = { real: number, imag: number } // Hypothetical syntax
constructor(value: Complex.Input) {
// …
}
}
// index.ts
import { Complex } from './complex.ts'
const input: Complex.Input = {
real: 3,
imag: -2,
}
const complex = new Complex(input)
Since type
declarations aren't allowed directly inside a class, how can I achieve this behavior while keeping the type definition closely associated with the class? Is there a recommended way to encapsulate such types within a class in TypeScript?
Allowing type
aliases inside a class
is a missing feature of TypeScript, requested at microsoft/TypeScript#7061. Until and unless this is implemented (and although it has a few hundred upvote I don't see any indication that it ever will be) you can work around it with declaration merging. If you want Complex.Input
to be a type, you're effectively using Complex
as a namespace
. So you can declare it as such and TypeScript will merge them automatically:
export declare namespace Complex {
export type Input = { real: number, imag: number }
}
export class Complex {
constructor(value: Complex.Input) {
// …
}
}
This workaround is mentioned in microsoft/TypeScript#7061 by the TypeScript team dev lead as being neither "terribly great" nor "horrible". It doesn't see to be very common to do this, but it's not bad practice either.
I can't really speak authoritatively about whether or not you should care about linter rules like @typescript-eslint/no-namespace
, as it is subjective. In my opinion the argument against namespace
is that it's an older alternative to JavaScipt modules. But declare namespace Complex
doesn't create any runtime code; it's fully erased with the rest of the type system... so it's not necessarily in violation. There's no "TypeScript internal module" at runtime that you'd be encouraged to replace with a JavaScript module. You might want to enable the allowDeclarations
option so that namespace
s are only flagged if you're using them to generate runtime code. But again, this part is opinion and others might disagree without anyone being objectively correct.