TypeScript 4.4: The Good, The Bad and The Not So Bad
Tags: typescript, javascript
Published: 2021-08-17 04:59:46

_by author [Arek Nawo](https://blog.openreplay.com/authors/arek-nawo)_

On August 12th, **TypeScript 4.4 Release Candidate** (RC) [was announced](https://devblogs.microsoft.com/typescript/announcing-typescript-4-4-rc/). This means that the changes have been finalized, and an official, stable release is coming shortly after a few additional bug fixes.

Let’s go through what’s new, how it could impact your development experience, and how to give it a try right now!

## Breaking changes

We’ll start by summarising the [breaking changes](https://devblogs.microsoft.com/typescript/announcing-typescript-4-4-rc/#breaking-changes). In this release, there are a few of them that aren’t major but could still break your code:


- You can no longer initialize `abstract` properties inside `abstract` classes. You can only define their type.
- `Promise` checks have been improved. TS will now remind you to include `await` when you’re `if`-checking the `Promise` results in more cases than before to prevent unintended always-truth checks.
- `catch` parameters are now `unknown` instead of `any` by default when either `\--strict` or new `\--useUnknownInCatchVariables` flag is on.
- `this` value is disregarded when calling imported functions to align with ECMAScript Modules specification on all available module systems (ESM, AMD, CommonJS, etc.)
- `lib.d.ts` has changed to be in line with current specifications (especially the `lib.dom.d.ts` with all changes [noted here](https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029#issuecomment-869224737))

With that out of the way, let’s see what big new features you can expect!

## Improved type guard detection

Probably the most important feature of TypeScript 4.4 is *“control flow analysis of aliased conditions and discriminants”*.

That means that from now on, **aliased type guards**, even with discriminated unions, will be properly analyzed and used for narrowing the given type.

In previous TS versions, the following wouldn’t work.


```javascript
const example = (arg: string | number) => {
  const isString = typeof arg === "string";

  if (isString) {
    return arg.toUpperCase(); // Error
  }

  return arg.toPrecision(); // Error
};
```


The `isString` condition wasn’t able to let TS know that, when it’s true, the `arg` is a `string`. As a result, TS gave an error when you used type-specific methods and properties, still thinking that `arg`‘s type is `string | number`. Only placing the condition in the `if` statement directly was properly interpreted.

With improved control flow analysis, that will no longer be an issue in TS 4.4. Furthermore, TS will also detect proper types when dealing with discriminated unions, even when performing checks on destructed properties!


```javascript
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; sideLength: number };

const area = (shape: Shape): number => {
  const { kind } = shape;
  const isCircle = kind === "circle"; // shape.kind === "circle" will also work

  if (isCircle) {
    // Circle
    return Math.PI * shape.radius ** 2;
  }
  // Square
  return shape.sideLength ** 2;
};
```


Up to a certain depth, TS will also recognize more complex, combined conditions and narrow the type accordingly.


```javascript
const example = (x: string | number | boolean) => {
  const isString = typeof x === "string";
  const isNumber = typeof x === "number";
  const isStringOrNumber = isString || isNumber;

  if (isStringOrNumber) {
    x; // string | number
  } else {
    x; // boolean.
  }
};
```


These improvements are really great! TS developers will now be able to nicely layout and annotate complex conditions without putting everything in their `if` statements or using direct type assertions.

## More versatile index signatures

Another great improvements have to do with index signatures. You’ll no longer be limited to just `number` and `string`. Now, `symbol` and template string patterns will also be allowed.


```javascript
interface Colors {
  [sym: symbol]: number;
}

const red = Symbol("red");
const green = Symbol("green");
const blue = Symbol("blue");
const colors: Colors = {};

colors[red] = 255;
colors[red]; // number
colors[blue] = "da ba dee"; // Error
colors["blue"]; // Error
```


`symbol` index signatures are a nice addition. However, in my opinion, **template string pattern** index signatures are much more interesting! This will let you narrow the index signature to a certain pattern, allowing for complex type definitions like never before!


```javascript
interface Example {
  a: number;
  b: string;
  [prop: `data-${string}`]: string;
}

const test1: Example = {
  a: 1,
  b: "example",
  "data-test": "example",
};
const test2: Example = {
  "data-test": "example",
}; // Error (no "a" and "b")
const test3: Example = {
  a: 1,
  b: "example",
  test: "example",
}; // Error ("test" not accepted)
```


If you’ve ever wanted to use index signature but narrow it down from general `string`, this update will be huge for you!

On top of all that, **union index signatures** will also be allowed. Any combination of `string`, `number`, `symbol`, and string template pattern is acceptable.


```javascript
interface Example {
  [prop: string | number]: string;
}
```

## Open Source Session Replay
Debugging a web application in production may be challenging and time-consuming. [OpenReplay](https://github.com/openreplay/openreplay) is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue. 
It’s like having your browser’s inspector open while looking over your user’s shoulder.
OpenReplay is the only open-source alternative currently available.

![OpenReplay](https://raw.githubusercontent.com/openreplay/openreplay/main/static/replayer.png)

Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://github.com/openreplay/openreplay).


## Exact optional property types

Apart from `\--useUnknownInCatchVariables`, one more flag has been introduced - `\--exactOptionalPropertyTypes`.

With this flag turned on, TS will no longer allow initializing optional properties with `undefined`.


```javascript
interface Example {
  a: string;
  b?: number;
}

const test: Example = {
  a: "example",
  b: undefined, // Error if --exactOptionalPropertyTypes is turned on
};
```


Such behavior determining whether the property actually is present on the object (with `undefined` value or otherwise) is useful in several cases.

When using, e.g., `Object.assign`, or object spread (`{ …obj }`), properties with `undefined` are actually handled differently compared to truly non-existent properties. Depending on the implementation, the same can be true for your code as well.

To allow for `undefined` with `\--exactOptionalPropertyTypes` turned on, you’ll have to explicitly include `undefined` in a union type. Without the flag, such behavior is automatic.


```javascript
interface Example {
  a: string;
  b?: number | undefined;
}

const test: Example = {
  a: "example",
  b: undefined, // Works correctly (even with --exactOptionalPropertyTypes on)
};
```


Because this flag can cause issues to arise in both your code and 3rd-party definitions (e.g., from DefinitelyTyped), it’s not included with `\--strict` and thus is opt-in and non-breaking.

If you feel like that could help in your codebase, turn on this flag, along with `\--strictNullChecks` to opt-in.

## Static blocks in classes

The last big new feature are `static` blocks.

It’s an upcoming ECMAScript feature that’s currently a [stage 3 proposal](https://github.com/tc39/proposal-class-static-block). `static` blocks allow for a more complex initiation process of `static` class members.


```javascript
class Example {
    static count = 0;

    // Static block
    static {
        if (someCondition()) {
            Example.count++;
        }
    }
}
```


Although the above was already possible, this feature makes the process simpler and much more elegant by allowing for the initiation block to be right inside the class definition.

Previously, such logic had to be put outside of the class definition, making it feel separate and cumbersome.


```javascript
class Example {
  static count = 0;
}

if (someCondition()) {
  Example.count++;
}
```


Apart from that, `static` blocks also have the advantage of allowing access to private static and instance fields (given how they’re part of the class’ definition), providing an opportunity for sharing their values with other classes or functions available in the same scope.


```javascript
let exampleFunc!: (instance: Example) => number;

class Example {
  static #accessCount = 0;
  #initial: number;
  constructor(input: number) {
    this.#initial = input * 2;
  }

  static {
    exampleFunc = (instance: Example) => {
      Example.#accessCount++;

      return instance.#initial
    }
  }
}

if (exampleFunc) {
  exampleFunc(new Example(2)); // 4
}
```
## Performance improvements

Aside from new features and breaking changes, as always, there are a few notable [performance improvements](https://devblogs.microsoft.com/typescript/announcing-typescript-4-4-rc/#perf-improvements):


- Faster declaration emits thanks to additional caching.
- Conditional path normalization reduces the time it takes the compiler to normalize paths it’s working with, thus loading faster.
- Faster path mapping for `paths` in `tsconfig.json`, thanks to additional caching, brings significant performance improvements.
- Faster incremental builds with `\--strict` thanks to fixing a bug causing unnecessary type-checking on every subsequent build.
- Faster source map generation for big outputs
- Faster `\--force` builds thanks to reduced unnecessary checks

## Intellisense improvements

The area that TS is most well-known for - **intellisense** (aka autocompletion/editor support) has also seen some improvement.

As TS suggestions get more confident, from 4.4, it’ll automatically issue **spelling suggestions for pure JavaScript** files without `checkJs`, or `@ts-check` turned on. These will be non-invasive, *“Did you mean…?”* type of suggestions.

For other visible improvements, TS can now show **inline hints**, aka “ghost text”. This can apply to everything from parameter names to inferred return types.


![TypeScript inline hints](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f7c8reskbm6uuns5fz8b.png)


Also improved are **suggested import paths**. Instead of unruly, relative paths like `node_modules/…`, TS will display paths you actually use - like `react` instead of `node_modules/react/..` or something. A cosmetic, but welcomed change.


![Improved suggested import path](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ft1t509n6stn2ze2nwx.png)

## Test drive

With all these great features, you’re likely wondering when you’ll be able to use them.
Your best bet would be to wait until stable release in the [next few weeks](https://devblogs.microsoft.com/typescript/announcing-typescript-4-4-rc/#whats-next). This way, you won’t have to deal with unresolved bugs and other issues.

However, if you want to test drive the RC version right now, you can get it from [NPM](http://npmjs.com/package/typescript):


```
npm install typescript@rc
```

Then, if necessary, select it for use in your IDE/code editor of choice.

Naturally, **VS Code** would provide you with the best experience, and with [VS Code Insiders](https://code.visualstudio.com/insiders/), you’ll get the latest TS version out-of-the-box.

## Bottom line

So there you have it! Tons of improvements are coming with TS 4.4, and even more, are already planned.

If you’re a TS user, this will be a good update for you. It’ll for sure improve your development experience even further than TS already does. And if you’re not already using TS, maybe it’s the right time for you to give it a try?