Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript automatic generic type without explicitly declare the type

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?

like image 335
Mohammed Al-qurafi Avatar asked Sep 14 '25 01:09

Mohammed Al-qurafi


1 Answers

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(...)).

Update for TS4.7+:

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+


Previous answer for TS4.6-:

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-

like image 180
jcalz Avatar answered Sep 17 '25 00:09

jcalz