I'm trying to create a configuration function for executing set of functions with a predefined context. Here are my types:
type Config<T> = {
context: T;
};
type Hooks<T> = {
hooks: T;
};
type FunctionWithThis<T> = (this: T, ...args) => any;
In my configure function I want to merge context and my target functions, but pass context parameter as this
to functions:
const configure = <TContext extends Object,
THooks extends Record<string, FunctionWithThis<TContext>>>
(config: Config<TContext> & Hooks<THooks>) => {
const result = {
get data() { return config.context; }
};
Object.entries(config.hooks).forEach((action) => {
result[action[0]] = (...args) => action[1].call(config.context, ...args);
});
return result as { data: TContext; } & THooks;
};
It can be used like this:
const engine = configure({
context: {
foo: 12
},
hooks: {
log() {
console.log(this.foo); // this.foo is typed correctly here
}
}
});
const data = engine.data;
engine.log(); // error here
VS Code gives me following error in the last line:
The 'this' context of type '{ data: { foo: number; }; } & { log(this: { foo: number; }): void; }' is not assignable to method's 'this' of type '{ foo: number; }'. Property 'foo' is missing in type '{ data: { foo: number; }; } & { log(this: { foo: number; }): void; }' but required in type '{ foo: number; }'.
I believe that it means that I'm trying to call log
function in the wrong context.
Is it possible to change context type for each function in the output?
Or maybe there is another way to solve it?
Since
THooks
must have members of type(this: T, ...args: any[]) => any
, the functions onTHooks
will requirethis
to be of typeTContext
. And while your function does ensure thethis
will indeed be ofTContext
, the types of the retuned functions don't reflect this. So when calling one of these functions onengine
, ts looks at the type ofenigine
and sees it is not the appropriatethis
forlog
and issues an error.Since you pass in
this
inside your implementation, you need to erase thethis
parameter from the result. You can do this with a mapped conditional type:Playground Link