镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Exposing type and object from class factories. #13798

Description

Following the great mixin implementation by Anders Hejlsberg (@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

  1. changed the title [-]Mixin [/-] [+]Exposing type and object from class factories.[/+] on Feb 1, 2017
  2. justinfagnani commented on Feb 1, 2017

    @justinfagnani

    You can almost accomplish this now, but with an extra empty prototype in the prototype chain, if you do:

    export class TaggedPoint extends Tagged(Point) {}
  3. shlomiassaf commented on Feb 1, 2017

    @shlomiassaf
    Author

    Yes, but that's not user friendly... for a library it doesn't make sense...

  4. mhegazy commented on Feb 1, 2017

    @mhegazy
    Contributor

    why not:

    export const TaggedPoint: Constructor<Tagged> & typeof Point = Tagged(Point);
    export type TaggedPoint = Tagged & Point;
  5. shlomiassaf commented on Feb 1, 2017

    @shlomiassaf
    Author

    Mohamed Hegazy (@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 :)

  6. mhegazy commented on Feb 1, 2017

    @mhegazy
    Contributor

    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.

  7. shlomiassaf commented on Feb 1, 2017

    @shlomiassaf
    Author

    Mohamed Hegazy (@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 :)

  8. shlomiassaf commented on Feb 1, 2017

    @shlomiassaf
    Author

    That's even more of an issue when the mixins are multilpe..

    export const MyClass = Mixin(A, B, C, D);

    Now you also need to export this type...

  9. mhegazy commented on Feb 1, 2017

    @mhegazy
    Contributor

    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.

  10. justinfagnani commented on Feb 1, 2017

    @justinfagnani

    Mohamed Hegazy (@mhegazy) that's a great issue, I hadn't seen it before. With something like #6606 we could write:

    const M = <T extends Constructor<{}>>(superclass: T) => class extends superclass {}
    type M = typeof M(Object);

    and not have to duplicate the class expression in a named interface. That's a big improvement.

  11. shlomiassaf commented on Feb 1, 2017

    @shlomiassaf
    Author

    Mohamed Hegazy (@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.

    export class User = RestMixn()

    Maybe making it simple

    export const User = RestMixn()

    If RestMixn() returns a class, automatically set the type as if

    export type User = ...;

    Was done by the developer....

  12. mhegazy commented on Feb 2, 2017

    @mhegazy
    Contributor

    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 as var myTaggedPoint: typeof new TaggedPoint();, just 10 characters more, but not too bad.

  13. shlomiassaf commented on Feb 2, 2017

    @shlomiassaf
    Author

    Mohamed Hegazy (@mhegazy) I agree on the design goal.

    But,

    • I don't think this is valid: var myTaggedPoint: typeof new TaggedPoint();
    • Even if valid, 10 chars is a lot when its in ctor, methods, etc... alot...
    • I need to expose types, these can be assigned as class members, part of interfaces etc...

    I understand the limitation, i'm trying to find a solution.

    What about internal logic:

    export const User = RestMixn()

    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...

  14. mhegazy commented on Feb 2, 2017

    @mhegazy
    Contributor

    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.html

  15. shlomiassaf commented on Feb 2, 2017

    @shlomiassaf
    Author

    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...

    :)

  16. aluanhaddad commented on Feb 2, 2017

    @aluanhaddad
    Contributor

    As we are talking about functions that are called to produce classes parametrically, when it comes to exporting the resulting values along with their types in a convenient and discoverable manner, I think it could be good to look at something like #13231. Since these classes are imperatively defined, exporting them seems like it may well fall under the domain of implementing a module interface.

  17. added
    Out of ScopeThis idea sits outside of the TypeScript language design constraints
    SuggestionAn idea for TypeScript
    and removed
    Needs InvestigationThis issue needs a team member to investigate its status.
    on Sep 18, 2019
  18. RyanCavanaugh commented on Sep 18, 2019

    @RyanCavanaugh
    Member

    New runtime class syntax as proposed here should be handled by TC39

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Out of ScopeThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions