Description
Following the great mixin implementation by @ahejlsberg (#13743)
Having a mixin function returns a class is powerful but requires manual type declaration.
interface Tagged {
_tag: string;
}
class Point {
constructor(public x: number, public y: number) {}
}
type Constructor<T> = new(...args: any[]) => T;
function Tagged<T extends Constructor<{}>>(Base: T) {
return class extends Base {
_tag: string;
constructor(...args: any[]) {
super(...args);
this._tag = "";
}
}
}
export const TaggedPoint: Constructor<Tagged> & typeof Point = Tagged(Point);
const tPoint: TaggedPoint; // ERROR: Cannot find name 'TaggedPoint'
We can't use TaggedPoint
as a type, unless we:
export type TaggedPoint = Constructor<Tagged> & typeof Point;
const tPoint: TaggedPoint; // NOW OK
Using classes we can
export class Customer extends Tagged(Person) {
accountBalance: number;
}
Which exposes the type but forces the developer to extend
the Customer
class.
My feature request is allowing:
export class TaggedPoint = Tagged(Point);
Which will be just sugar for:
export const TaggedPoint = Tagged(Point);
export type TaggedPoint = Constructor<Tagged> & typeof Point;
Maybe even:
export class TaggedPoint = Tagged(Point) { // user can extend here...
}
but that's not the main point...
The motivation for this is a more robust mixin mechanism that revolves around the base class and allow mixin's to know about it, using generic it provides powerful combinations.
So I can have
RestMixin(Base, Mixin1, Mixin2);
And the return type can have members from Mixin1 knowning about Base, using interfaces one can even set static members of a mixin to return T which is the instance of RestMixin(Base, Mixin1, Mixin2)
all together.
Activity
[-]Mixin [/-][+]Exposing type and object from class factories.[/+]justinfagnani commentedon Feb 1, 2017
You can almost accomplish this now, but with an extra empty prototype in the prototype chain, if you do:
shlomiassaf commentedon Feb 1, 2017
Yes, but that's not user friendly... for a library it doesn't make sense...
mhegazy commentedon Feb 1, 2017
why not:
shlomiassaf commentedon Feb 1, 2017
@mhegazy yes, you are right, I copy pasted it from the samples in #13743
Anyway, it's not the issue, you still need to export the type :)
mhegazy commentedon Feb 1, 2017
a class represents 1. a value, the constructor function, and 2. a type, which is the instance type, you can get the same behavior by exporting a value and a type with the same name. lib.d.ts does this for most declarations e.g.
HTMLElement
.shlomiassaf commentedon Feb 1, 2017
@mhegazy Yes, I know that, this is what I demonstrated in the samples.
We now have class factories that the developer need to manually export when using a class factory function...
I can live with the library author doing it... but if the developer has to, its not pretty :)
shlomiassaf commentedon Feb 1, 2017
That's even more of an issue when the mixins are multilpe..
Now you also need to export this type...
mhegazy commentedon Feb 1, 2017
This is not specific to mixins, the issue is that there is no convienient way to refers to the return type of a function or a constructor. #6606 Is the issue tracking this.
justinfagnani commentedon Feb 1, 2017
@mhegazy that's a great issue, I hadn't seen it before. With something like #6606 we could write:
and not have to duplicate the class expression in a named interface. That's a big improvement.
shlomiassaf commentedon Feb 1, 2017
@mhegazy #6606 still requires the extra type declaration.
Now that class factories can behaves as classes, the only missing piece is how to expose the runtime + design time result natively as if they were declared as classes.
Maybe making it simple
If RestMixn() returns a class, automatically set the type as if
Was done by the developer....
mhegazy commentedon Feb 2, 2017
We really do not like introducing new syntax that can conflict with ES committee future direction (see design goal #4). We do not have a similar issue with the type space though.
I think you do not need to expose additional types, you just
export const TaggedPoint = Tagged(Point);
and that is that. your users now can use it as a constructor, e.g.import {TaggedPoint } from "mod"; new TaggedPoint();
, and if they choose to talk about the type, they can just refer to it asvar myTaggedPoint: typeof new TaggedPoint();
, just 10 characters more, but not too bad.shlomiassaf commentedon Feb 2, 2017
@mhegazy I agree on the design goal.
But,
var myTaggedPoint: typeof new TaggedPoint();
I understand the limitation, i'm trying to find a solution.
What about internal logic:
Seeing that
RestMixin()
returns a class, TypeScript can automatically export the type with the same name if such does not exit, it shouldn't break anything, it can be lazy so the type is set once imported or used...mhegazy commentedon Feb 2, 2017
A
const
is not a type, it is a value, and it is possible that there is a type with the same name, e.g. http://www.typescriptlang.org/docs/handbook/declaration-merging.htmlshlomiassaf commentedon Feb 2, 2017
:)
8 remaining items