If I have an interface with two functions. one function returns a value. I want to use the type of value in another function in the same interface. I did use generics but I have to explicitly declare the type every time I create new object. but It could be done without it. For example:
interface MyObject<T> {
create(): T
update(value: T): void // use value type defined in create method
}
and here we define new object using the interface above:
let test: MyObject<string> = {
create() {
return "Hello"
},
update(value) {
// do something with value
},
}
It's possible to write MyObject
without <string>
. as the value type already been defined in create
method?
You would like the compiler to infer string
for the generic type parameter T
. The only place such generic type parameter inference occurs is when you call a generic function, so if you want this behavior you will need to refactor from a type annotation (const test: MyObject<string> = ...
) to calling a helper function (const test = toMyObject(...)
).
It looks like microsoft/TypeScript#48358 will be released with TypeScript 4.7, at which point the compiler will indeed be able to contextually type method parameters from type parameters inferred from previously defined properties. At that point the following will work:
const toMyObject = <T,>(o: MyObject<T>) => o;
let test = toMyObject({
create() {
return "Hello"
},
update(value) {
value.toUpperCase()
},
})
Note that this depends on the order of the members of the object literal, so because you need T
inferred from create()
before you can use it in update()
, you need create()
to be earlier than update()
in the object. So the following breaks:
let test2 = toMyObject({
update(value) {
value.toUpperCase() // error
},
create() {
return "Hello"
},
});
Playground link for TS4.7+
Furthermore, you want the compiler to infer the type of the value
callback parameter of the update
method. This type of inference is called contextual typing.
Unfortunately, contextual type inference of callback parameters and generic type parameter inference do not play well together when both depend on the same object. This is a design limitation of TypeScript. See microsoft/TypeScript#38872 for more information.
In order to get both contextual type inference and generic type parameter inference then, your helper function will need to split the input object into two pieces: one for create
so that the generic type parameter T
can be inferred, and the other for update
so that the callback parameter value
's type can be inferred.
That gives us this:
const toMyObject = <T,>(
create: () => T,
update: (value: T) => void
): MyObject<T> => ({ create, update });
Let's test it:
let test = toMyObject(
() => { return "Hello" },
(value) => { console.log(value.toUpperCase()); }
);
// let test: MyObject<string>
Looks good. The compiler infers that test
is of type MyObject<string>
as desired, and the callback parameter value
is also inferred as string
(as evidenced by the fact that you can call value.toUpperCase()
without error in --strict
mode).
Playground link to code for TS4.6-
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With