I'm working on a factory and I need to eventually add custom methods.
Thanks to the community I got a bit closer to the desired solution with this answer, but I need to add a useless argument to make it working as expected.
Here is a reworked code from the previously said answer.
type Base = { id: string }
type Factory<E> = new () => E;
function factory<E extends Base>(name: string): Factory<E>;
function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S) => unknown>>(
name: string, methods: M, useless?: (this: E & M) => void
): Factory<E & M>;
function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S) => unknown>>(
name: string, methods?: M, useless?: (this: E & M) => void
): Factory<E> {
const ret = function(this: Base) {
this.id = "0"
};
Object.defineProperty(ret, "name", { value: name });
if(methods) Object.assign(ret.prototype, methods);
return ret as unknown as Factory<E>;
}
const T2 = factory(
"T2",
{
test: function(repeat = true) {
if(repeat) this.test(false);
return "test";
}
},
function() {} // If we comment out this line, the TypeScript magic breaks (but not the JavaScript one)
);
const t2 = new T2();
console.log(t2, t2.id, t2.test());
If we comment out the useless
argument, the test
method is no longer visible inside its definition!
Any idea on how to make this works even without the useless
argument?
Here is a playground to experiment.
CodePudding user response:
methods
is a record where each key is a string
and value is a function. It means that you need to overload each function of methods
record.
Consider this example:
type Base = { id: string }
type Factory<E> = new () => E;
function factory<E extends Base>(name: string): Factory<E>;
function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S) => unknown>>(
name: string, methods: M & Record<string, (this: E & M) => void>
): Factory<E & M>;
function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S) => unknown>>(
name: string, methods?: M
): Factory<E> {
const ret = function (this: Base) {
this.id = "0"
};
Object.defineProperty(ret, "name", { value: name });
if (methods) for (const method in methods) Object.defineProperty(ret.prototype, method, { value: methods[method] });
return ret as unknown as Factory<E>;
}
const T1 = factory("T1");
const t1 = new T1();
console.log(t1, t1.id);
const T2 = factory(
"T2",
{
test: function (repeat = true) {
this.foo()
if (repeat) this.test(false);
return "test";
},
foo: function (repeat = true) {
this.test()
return "foo";
}
},
);
const t2 = new T2();
console.log(t2, t2.id, t2.test());
Please keep in mind that function intersection produces function overloading. This is exactly what I did here M & Record<string, (this: E & M) => void>
. I have merged M
with a record where each key is a string
and value is a function which previous was useless
P.S. I have added foo
just for testing, don't use it in your code without condition statement