TypeScript type inference with const assertions and the infer keyword

JavaScript TypeScript

TypeScript works in a way that automates a lot of the work for us. We don’t have to write types every time, because the compiler works hard to derive them from the context. In this article, we look into more complex cases that involve the infer keyword and const assertions.

The basics of type inference

First, let’s look into some elementary examples of type inference.

Variable defined in such a way has a type of  . We didn’t give the compiler any tips on how we will use it.

This time, we defined an initial value for our variable. TypeScript can figure out that it is a  , and therefore we now have a adequately typed variable.

A similar thing happens with functions.

In the code above, we don’t have to indicate that our   function returns a number. The TypeScript compiler is fully aware of it.

Inference in generics

Powerful usage of the above concept is built into generics.

If you want to know more about generics, check out TypeScript Generics. Discussing naming conventions and More advanced types with TypeScript generics

When creating generic types, we can do lots of useful stuff. Type inference makes it more elegant and easier to use.

When using the above generic function, we don’t have to pass the types explicitly.

The above is also very useful when creating generic React components. If you want to know more, check out Functional React components with generic props in TypeScript

The use-case of the infer keyword

One of the more advanced features that come to mind when discussing inference is the   keyword.

First, let’s declare such a function:

Above, we call a function and return its value.

The above gives us the return value of the   function provided with the maximum value of 100. There is a minor problem with the above, though. Nothing prevents us from not respecting the types of the arguments of the  .

Since TypeScript supports spread and rest parameters in higher-order form, we can resolve the above issue.

Now we say that the   function can handle an array of arguments in any form, but it has to match the provided function.

Argument of type ‘”100″‘ is not assignable to parameter of type ‘number’.

In fact, by doing the above, we’ve just created a tuple. In TypeScript, tuples are fixed-length arrays whose types are known but don’t have to be the same.

Introducing the infer keyword

Now, imagine that instead of getting the return value of a function, we only want to get the return type.

The above type is still incomplete. We need to figure out a way to determine the return value. We could be explicit with it, but that would defeat the point.

Instead of doing it explicitly, we can ask TypeScript to infer the return type for us. The infer keyword is permitted only in the conditional types. This is the reason our code sometimes can get a bit messy.

In the above code, we:

  • state, that the FunctionType extends 
  • we say that the FunctionReturnType is a conditional type
    • inside we declare the ReturnType and assign it with the return type of our function
      •  results in assigning the return type to the ReturnType variable
  • we check if FunctionType extends 
    • if the above condition is not met, we assign   to FunctionReturnType
    • since the above condition is always met, we assign the ReturnType to the FunctionReturnType

By doing all of the above, we can extract the return type of any function.

The above is such a common case that TypeScript has a built-in utility type called ReturnType that works in the same manner.

Const assertions

Another thing regarding the inference is the difference between the   and   variable declaration.

Our   is a string. It means that it can hold any string value.

Our   is a string literal. We can consider it as a subtype of a string.  This pull request describes it as follows:

A string literal type is a type whose expected value is a string with textual contents equal to that of the string literal type.

We can alter the above behavior. TypeScript 3.4 introduces a new interesting feature called const assertions.

Now our   is a string literal. The const assertions also come in handy when implementing immutability. Let’s consider the following object:

In JavaScript,   means that we can’t reassign the value that the   variable holds. We can, on the other hand, modify the object.

Currently, the object holds the following type:

We can use const assertion to treat is as immutable.

Now our type changed. Our strings changed to string literals instead of strings. Not only that, but all of the properties are also .

It gets even more powerful with arrays.

The above array has the type of  . We can make it into a tuple with a const assertion:

Now instead of a regular array, our list has a type of  .

The above behavior also applies to more nested structures. Let’s consider the example that Anders Hejlsberg used in his TSConf 2019 talk:

Our   array is now deeply immutable:

Summary

In this article, we’ve gone through some more advanced examples of type inference. It included the infer keyword and the const assertions. They can come in handy in some more sophisticated cases. It might prove to be useful, for example, when dealing with immutability and doing functional programming. For more fundamental examples, check out this Type Inference Guide by Tomasz Ducin.

avatar
  Subscribe  
Notify of