10 TypeScript features you might not be using yet or didn't understand

Oleh Baranovskyi
5 min readJan 17, 2022

TypeScript has many features that we’re using on a regular basis or, to put in another way, too many features to keep in mind. Releases are pretty often. It means that new features are constantly arriving. It’s pretty easy to get lost in documentation or miss some releases or features. And I’m not an exception. I’m taking a step back and revisiting documentation after some time to find something that I probably heard of but wasn’t using.

In this article, we’ll go through all features that I wasn’t using from the time they were released but using them right now.

  1. How and when to use an unknown type (or unknown vs any)
  2. Indexed access types (Lookup type)
  3. The infer keyword
  4. Assert functions
  5. When to use never
  6. Using const assertion
  7. The override keyword
  8. static blocks in classes
  9. Deprecation support
  10. Instance and static index signatures

1. How and when to use an `unknown` type (or `unknown` vs `any`)

An unknown type is a more safe version of any.

It can be significantly helpful when moving from a JavaScript to a TypeScript project. During the transition, it might help you understand code better and stay safer.

Before we try to understand the difference between the unknown and any type, let’s look at similarities.

We can assign whatever we want to variables with any and unknown type and everything will work without errors.

But this is where the similarities end up.

Unlike any, anunknown type prevents us from doing anything we want with the value type that we don’t know.

Consider the following example:

or even this:

In this case, we won’t get a build time error. Instead, we will get a runtime error, which is worse.

To put it simply in other words — when we use type any, we say to the compiler — “Do not check this code; I know what I’m doing.”
A TypeScript compiler, in turn, doesn’t make any type assumptions on it. Therefore, we can’t prevent runtime errors. And most of the time, it’s not what we want.

With an unknown type, we will get a build time error. In addition, an IDE will highlight an error right away.

Let’s take a look at it in action:

Every time we try to do something with this, we get an error.

In order to work with the unknown, we need to narrow the type down.

Using type assertion:

Using type guard:

Using self-defined type guard:

Using assertion function:

2. Indexed access type (Lookup type)

We can use an indexed access type to look up a specific property on another type. Let’s take a look at the example:

Here we create a new type such as id, Session, Street, Addons from the existing object.

or we can use it directly in a function like this:

3. The `infer` keyword

The infer keyword allows you to deduce a type from another type within a conditional type.

Here’s an example:

Here we deduce the type of addon to a new separate type. Awesome huh?

4. Assert functions

There’s a specific set of functions that throw an error if something unexpected happened. They’re called “assertion” functions.

In the example above, we check whether the value is a string. In this case, we do nothing. Otherwise, an error is thrown.

Here is a more complex example that shows how to assert objects and inner objects:

here is it in action:

5. When to use `never`

The never type represents a value that is never observed.

In a return type, this means that the function throws an exception or terminates the execution of the program.

here is another example that uses promises:

and one more, where the program won’t end up:

never also appears when TypeScript determines there’s nothing left in a union.

6. Using `const` assertion

When we construct new literal expressions with const assertions, we can signal to the language that:

  • no literal types in that expression should be widened (e.g. no going from "hello" to string)
  • object literals get readonly properties
  • array literals become readonly tuples

and here is a const assertion in action:

In addition, with const assertion help we can transform an array of strings into a string literal union type:

We use [number] after the roles array to tell TypeScript to grab all the numbered indexed values from the roles array.

7. The `override` keyword

Since version 4.3 now, we can tell in child classes that we’re overriding existing behavior by using the override keyword.

As this is a brand new feature, you might be using overrides in the old fashion. I would recommend changing the configuration to make this rule mandatory. You simply need to change noImplicitOverride to true.

8. `static` blocks in classes

TypeScript 4.4 brings support for static blocks in classes, where you can write more-complex initialization code for static members.

9. Deprecation support

Not a new feature, but I found that I wasn’t using it but should.

To mark something as deprecated, we can use JSDoc annotation:

IDE recognizes and suggests not to use the deprecated parts. Please take a look at deprecated methods and classes.

They all are crossed out to show you shouldn’t use them anymore.

We also can see the reason why it’s deprecated:

10. Instance and static index signatures

Index signatures allow us to set more properties on a value than a type explicitly declares.

Here is an example that uses a static index signature.

Conclusion

I hope you found this article interesting and came across new techniques. Please feel free to reach out if you have any questions.

Hit the 👏👏👏 button and subscribe, which will motivate me to write more articles.

--

--