Consider this class definition:
class MyClass<T extends Record<string, unknown> = any> {
constructor(
public params: {
methodOne: (data: Record<string, unknown>) => T;
methodTwo: (data: T) => void;
}
) {
// ...
}
}
// OK
const myClassInstance = new MyClass({
methodOne: (data) => {
return {
name: "foo",
lastName: "bar",
};
},
methodTwo: (data) => {
console.log(data.lastName); //ok
console.log(data.name); //ok
},
})
So what I'm trying to do is infer the data parameter in the methodTwo (which comes from the methodOne and this works correctly in this kind of setup, when arguments are created inline when instantiating the class instance.
Now I wanna do the same thing but instead of declaring the methodTwo directly in the params argument to the MyClass instance I want to have it somewhere else in the code, and this is where I get the dreaded (7022) error
// NOT OK
//'myClassInstanceTwo' implicitly has type 'any' because it does not have a type annotation
//and is referenced directly or indirectly in its own initializer.(7022)
const myClassInstanceTwo = new MyClass({
methodOne: (data) => {
return {
name: "foo",
lastName: "bar",
};
},
methodTwo: methodTwoExternal,
});
type MethodOneReturn<T extends MyClass> = ReturnType<T["params"]["methodOne"]>;
// 'data' is referenced directly or indirectly in its own type annotation.(2502)
function methodTwoExternal(data: MethodOneReturn<typeof myClassInstanceTwo>) {
console.log(data.lastName); //ok
console.log(data.name); //ok
}
Is there a way to make this work?
I'm open to changing the MyClass constructor signature, maybe even introduce a factory function for the class instantiation.
All I can think of here (assuming this pattern is something we need to support) would be to make some sort of builder that lets you set
methodOnefirst, then get an intermediate thing that lets you extract the typeTfrom it, and then lets you setmethodTwoand finally gives you aMyClass<T>instance. Something like:So
MyClass.builder.m1(method1).m2(method2)is the same asnew MyClass(method1, method2), but now you can pause atMyClass.builder.m1(method1)and inspect it for the type you care about. To make that easy I added a dummy/phantom__typeproperty, so that the type is liketypeof xxx.__typeinstead of something awful likeParameters<Parameters<typeof xxx.m2>[0]>[0].Let's see if it works:
Looks good.
Playground link to code