0

Say I have the following TypeScript code:

export const makeMyClass = function(a: string, b: boolean){

   const MyClass = function(){

   };

   MyClass.prototype.foo = function(){};

   MyClass.prototype.bar = function(){};

   return MyClass;

}

I am having trouble figuring out how to convert the code inside the exported factory function into TypeScript.

For example, if I do this:

 export const makeMyClass = function(a: string, b: boolean): MyClass {

       class MyClass {

       }

      // ...

      return MyClass;


  }

TypeScript complains saying that it can't find name 'MyClass'. Please assume that I need to use the exported closure makeMyClass for the sake of the question.

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • 1
    I think the only solution you can use is to declare an interface in an outer scope that the class implements, and then return a constructor for that interface. Could you flesh out the implementation of `MyClass` a little so I can show you what I mean? – jcalz Sep 22 '17 at 19:39
  • I don't want to add any extraneous details to the question, it just makes it harder for people to know what I am asking. I think I know what you are talking about, so if you don't mind adding an answer I will upvote. – Alexander Mills Sep 22 '17 at 19:46

1 Answers1

1

If you want to give a name to the type of the return value of makeMyClass() which is visible outside of the function, then you have to describe its structure as a type of interface in an outer scope. At least that's how I understand TypeScript's implementation of class expressions.

Here's how I would do it in your case. A class has an instance type (which describes instance properties and class methods) and a constructor type (which describes the constructor and any static methods), so we need to describe both:

export interface MyClassInstance {
  foo(): void;
  bar(): void; 
  // define the rest of the public interface here
}
export interface MyClassConstructor {
  new(): MyClassInstance;
  readonly prototype: MyClassInstance;
  // define any static methods here
}

Now you can declare that makeMyClass() returns the constructor type:

export const makeMyClass = function(a: string, b: boolean): MyClassConstructor {

  // implement the MyClassInstance/MyClassConstructor interfaces
  class MyClass {
    foo() {
      // ...
    }
    bar() {
      // ...
    }
  }

  return MyClass;
}

Note that this is repetitive, in that you declare the class structure both inside and outside the function. If you add a property or method to the interfaces, you will need to add a corresponding property to the class implementation. This seems to be unfortunately unavoidable, since you can't export types from inside functions.

Anyway, hope that helps; good luck!

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • thanks, yeah I might send this question over the aficionados at DefinitelyTyped because this seems like a common-ish problem. – Alexander Mills Sep 22 '17 at 23:47
  • Thanks for this answer, you might be able to answer this one too? https://stackoverflow.com/questions/46639573/create-prototype-function-with-different-scope-not-an-inline-function – Alexander Mills Oct 12 '17 at 18:45
  • I think in your answer here, MyClass needs explicitly extend or implement MyClassConstructor? – Alexander Mills Oct 12 '17 at 18:49
  • If by "explicitly" you mean you need to write "`class MyClass implements MyClassInstance`" or something, no. You just need to implement the static and instance properties and methods so that the compiler can verify that the returned class constructor is a `MyClassConstructor`. If the compiler is unhappy with you, it should tell you how and why `MyClass` is failing to be a `MyClassConstructor`. – jcalz Oct 12 '17 at 19:10