S
S
Sergey Erzhovich2022-02-21 23:45:05
OOP
Sergey Erzhovich, 2022-02-21 23:45:05

What is the best way to export a class so that it can be called as a function?

Let's say we have this code

class BaseLol {  /* ... */  }

class Lol extends BaseLol {
  constructor(public name: string) {
    super();
    this.name = name.toUpperCase();
  }
}

It is necessary that you can use it in this way, and that there would be hints on the parameters:
import lol from './lol';
const instance = lol('qwe');


You can export it like this, for example Or like this
export default (name: string) => new Lol(name);
export default (...args: ConstructorParameters<typeof Lol>) => new Lol(...args);


Вопрос в том можно ли сделать какой то хелпер что бы не прокладывать параметры каждый раз, хотелось бы что то типа такого:
function exportLol(classFromExport) {
  return (...args) => new classFromExport(...args);
}
export default exportLol(Lol);


но тогда нету подсказок по параметрам.

Плюс этот базовый класс может использоваться на проекте без тайпскрипта, что бы были хоть минимальные подсказки, можно переписать первую конструкцию таким образом:

class BaseLol {  
constructor() {
    return new Proxy(this, {
      get(target, prop) {
        if (prop === "init") {
          return (...args: any) => {
            const clone = Object.assign(
              Object.create(Object.getPrototypeOf(target)),
              target
            );
            clone.init(...args);
            return clone;
          };
        }
      },
    });
  }
/* ... */  
}

class Lol extends BaseLol {
  init(name: string) {
    this.name = name.toUpperCase();
    return this;
  }
}


И экспортировать потом таким образом:
export default new Lol().init;
Тогда можно использовать таким образом:
import lol from './lol';
const instance = lol('qwe');

И видны подсказки по параметрам.

Суть в том, чтобы было удобно писать классы без лишнего бойлерплейта, и использовать их внешне как функцию без оператора new.
Хз как лучше...

Answer the question

In order to leave comments, you need to log in

1 answer(s)
A
Aetae, 2022-02-22
@ Drilled-prog

Are you too lazy to write new? IMHO, this is overkill. Your hands will not fall off to write three extra characters, and it will be much clearer than incomprehensible unnecessary functions.
But the races are so desirable, then the type for your function is as follows:

function exportConstruct<P extends any[], T>(classFromExport: { new (...args: P): T; }): 
(...args: P) => T {
  return (...args) => new classFromExport(...args);
}

But so you lose for example an opportunity to use static methods of a class.
If you can use Proxy then you can just make the class callable:
function exportCallable<T extends { new (...args: any[]): any; }>(classFromExport: T) {
  return new Proxy(classFromExport, {
    apply(ctor, _, args) {
      return new ctor(...args);
    }
  }) as T & ((...args: ConstructorParameters<T>) => InstanceType<T>);
}

const Lol = exportCallable(class Lol extends BaseLol {
  constructor(public name: string) {
    super();
    this.name = name.toUpperCase();
  }
});

Lol('qwe');

Or you can just add a static method that will do the same:
abstract class Newable {
  static new<P extends any[], T>(this: { new (...args: P): T; }, ...args: P): T { 
    return (new this(...args)) as T
  }
}

class BaseLol extends Newable {  /* ... */ }

class Lol extends BaseLol {
  constructor(public name: string) {
    super();
    this.name = name.toUpperCase();
  }
}

Lol.new('qwe');

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question