nested class declaration static namespace
TypeScript currently allows writing
class Foo {
static Bar = class {}
}
where Foo.Bar is a constructor to an anonymous class. Unfortunately, there's not a good way to write the type of Foo.Bar, other than InstanceType<typeof Foo.Bar>. Trying to write simply Foo.Bar as a type fails because Foo is not a namespace. Alternatively, one can separately open the namespace and declare the type there as well namespace Foo { export type Bar = InstanceType<typeof Foo.Bar>; }, but this is repetitive and error-prone.
Allowing a declaration of the form
class Foo {
static class Bar {}
}
could potentially get over this issue by automatically defining the type Bar in the Foo namespace.
This is currently invalid syntax both in TS and in JS. It would be reasonable to also propose this as an extension to the static member fields proposal to TC39, though they don't make as much of a distinction as TypeScript does between const Foo = class {} and class Foo {} (only the latter declares a type), so the extra syntax is less important there.
Nested classes are particularly useful for two things: (1) readable APIs, and (2) encapsulation.
Foo with nested class Foo.Builder provides an unambiguously clearer API than separately exporting Foo and FooBuilder at the top level. Extending the latter example to its logical conclusion, you end up with enough exports that users will tend to import * as foo instead of import {Foo}, and thus write foo.FooBuilder all over the place, smurfing everything up.SortedSet class with private field elems could have a nested class SortedSet.Reversed that stores a reference to the SortedSet and reads its elems field directly, which could allow for a smaller public API for SortedSet since the reverse can read private data to perform symmetric functionality.As it currently stands, (2) is doable if you don't care to use the nested class as a type name, while (1) is very cumbersome in the current language.
export class Foo {
static class Builder {
private bar: number;
withBar(bar: number): Foo.Builder {
this.bar = bar;
return this;
}
build() {
return new Foo(this.bar);
}
}
// ...
}
This would essentially desugar to the same thing as
export class Foo {
static Builder = class { /* ... */ };
// ...
}
namespace Foo {
export type Builder = InstanceType<typeof Foo.Builder>;
}
My suggestion meets these guidelines:
This is already allowed with the TypeScript syntax
class Foo {
}
namespace Foo {
export class Bar {
}
}
Adding an additional syntax inside the class itself is a proposal that should be taken to TC39.
This is allowed, but has [a minor problem]: Bar cannot access the Foo class's privates like it could were it nested within class Foo. There's also the organizational problem that this distinction is unique to TypeScript, so it's unlikely TC39 would be interested in this, since as far as JavaScript is concerned, static class Bar {} is no different from static Bar = class {}, but since TypeScript does make a distinction between the two forms (in general), it leaves us in a bit of a fix.
@RyanCavanaugh is there any solution for this problem ?
Lobby es-discuss or TC39 about the importance of having this feature
Honestly that's a bit of a non-answer. The feature is already as supported as it can be as far as TC39 is concerned - the only reason it's necessary is due to differences TS imposes _outside_ of the EcmaScript standard (specifically, the fact that class expressions don't make types, and visibility - yes, we're getting #privates soon, but constructor visibility is also a thing, i.e. in the builder example).
specifically, the fact that class expressions don't make types
export class Foo {
static Builder = class {
private bar: number = 0;
withBar(bar: number): Foo.Builder {
this.bar = bar;
return this;
}
build() {
return new Foo(this.bar);
}
}
// ...
}
export namespace Foo {
export type Builder = InstanceType<typeof Foo.Builder>;
}
Hi @RyanCavanaugh Can you take a look on this option for this use-case
class ParentClass {
private var1 = 1;
static svar1 = 2;
ClassMember = ((rootThis) => class ChildClass {
access() {
console.log(ParentClass.svar1, rootThis.var1);
}
})(this);
}
Most helpful comment
This is allowed, but has [a minor problem]:
Barcannot access theFooclass's privates like it could were it nested within classFoo. There's also the organizational problem that this distinction is unique to TypeScript, so it's unlikely TC39 would be interested in this, since as far as JavaScript is concerned,static class Bar {}is no different fromstatic Bar = class {}, but since TypeScript does make a distinction between the two forms (in general), it leaves us in a bit of a fix.