Reputation: 635
I am trying to type a class generic from a method argument that will be called later. The generic type of the class would not be known until we called a method taking a generic argument. Then, for any other methods, the generic type would be passed on.
Honestly, for me, this seems so complex of functionality that I am not even sure TypeScript has a way of doing it...
Here's what I would like the behavior to look like:
class Class<T> {
foo = (bar: T[]) => {
/* ... */
return this;
};
baz = (cb: (qux: T) => void) => {
/* ... */
return this;
};
}
new Class() // Here, T should be unknown
.foo([{ name: "John", id: "123" }]) // Here, it should be infered that T is { name: string, id: string }
.baz((person) => null) // Here, person should have the type { name: string, id: string }
In this example, when we instantiate the Class
, T
should be unknown. Then, when we pass an array of objects to foo
, the T
should be inferred to be of that object type, since bar
is typed T[]
. Now that T
has been infered, qux
should be automatically typed when passing a function to baz
. ( baz(cb: ({ name: string, id: string }) => void)
)
Note that I do not want to have to pass a generic to the Class
as I want it to be inferred later. In other words, I don't want to do
new Class<{ name: string, id: string }>()
Thanks for your answers!
Upvotes: 3
Views: 1178
Reputation: 635
I guess writing the question acted as rubber duck debugging! About a few minutes after my post, I found this. However, it is not too pretty, so I am going to leave the thread open for a while in case a better idea comes up:
class Class<T> {
foo = <U extends T>(bar: U[]) => {
/* ... */
return (this as unknown) as Class<U>; // not pretty
};
baz = <U extends T>(cb: (qux: U) => void) => {
/* ... */
return (this as unknown) as Class<U>; // not pretty
};
}
new Class() // Here, T is unknown, as it should be
.foo([{ name: "John", id: "123" }]) // Here, T becomes U ({name: string, id: string})
.baz((person) => null); // Here, person is typed {name: string, id: string} Yay!
// This also works!
new Class() // Here, T is unknown, as it should be
.baz((person: { name: string; id: string }) => null) // Here, T becomes U ({name: string, id: string})
.foo([{ name: "John", id: "123" }]); // Here, bar is typed { name: string, id: string }[] Yay again!
By typing the methods with another generic type that extends the class' generic type, we can return this
and "safely" type it as Class<U>
, which now gives an actual type instead of unknown
.
The problem here is that this only works because we are returning this
while typing it with a new type Class<U>
. Thus, passing an object to a method that does not return this
would not modify it's generic type...
class Class<T> {
public data: T;
quux = <U extends T>(quuz: U) => {
this.data = quuz;
/* ... void */
};
}
const c = new Class();
c.quux({ name: "John", id: "123" });
c.data // still unknown
Upvotes: 1