Things to watch for in TypeScript 4.2 onwards
Tags: typescript, javascript
Published: 2020-12-30 13:02:33

TypeScript is still on 4.1 at the time of writing this, but here's a few features currently being worked on or discussed which I found interesting.

## Static index signatures

Index signatures usually look like this:

```ts
class Foo {
  // All keys have numeric values
  [key: string]: number;
}

const obj = new Foo();
obj['a']; // number
obj['b']; // number
```

However, what if we want to index into the class itself? Like `Foo['a']`. This is where "static index signatures" come in:

```ts
class Foo {
  // All static keys have numeric values
  static [key: string]: number;
}

Foo['a']; // number
Foo['b']; // number
```

## `querySelector` type inference

Currently, `querySelector` can infer the type of the element you're selecting if it is a simple selector:

```ts
document.querySelector('div'); // HTMLDivElement
```

However, this isn't possible right now with anything more complex:

```ts
document.querySelector('div p'); // Element
document.querySelector('div.foo'); // Element
```

There's discussion going on around improving the built-in types for this to "parse" the string you pass in and infer the correct type:

```ts
document.querySelector('div p'); // HTMLParagraphElement
document.querySelector('div.foo'); // HTMLDivElement
```

This'd be awesome but I do wonder what the performance hit would be. We will see...

## Leading/middle rest elements

You can currently do the following:

```ts
function test(...args: string[]) {}

test('a', 'b'); // fine
test('a', 5); // error
```

But you can't specify the surrounding elements. A new proposal would solve this and allow the following:

```ts
function test(...args: [...string[], number]): void;

function test2(...args: [number, ...string[], number]): void;

test('a', 'b'); // error
test('a', 'b', 5); // fine
test('a', 5, 'b'); // error

test2(5, 'a', 'b', 6); // fine
test2(5, 'a', 'b'); // error
```

This can be pretty useful to be more specific about what elements the arguments array should contain.

## `override` keyword

My favourite new feature, the `override` keyword and the associated `noImplicitOverride` compiler option.

This is exactly as you'd expect, it allows you to specify that you're overriding an inherited method:

```ts
class Foo {
    x(): number {
        return 5;
    }
}

class Bar extends Foo {
    override x(): number {
        return 6;
    }
}
```

With the `noImplicitOverrides` option enabled, this means we will get a compiler error if we _don't_ use the keyword on overridden methods.

This is great as I think method overrides are too weak right now. It is too easy to override a method without realising, or to forget a parent class has a same named method, and so on. Just a bit more strictness to catch some sneaky problems.

## `noPropertyAccessFromIndexSignature`

Enabling this compiler option means you can only access index signatures using square brackets:

```ts
interface Foo {
  b: number;
  [key: string]: number;
}

declare const test: Foo;

test.b; // fine
test.a; // error
test['a']; // fine
```

Comes down to personal preference, I suppose.

## Inferred template literal types

This one feels like it'll break a _lot_ of stuff:

```ts
const foo = ['a', 'b'];
const bar = foo.map(n => `${n}-xyz`);

// bar is now Array<`${string}-xyz`>
bar.push('foo-xyz'); // works
bar.push('xyz'); // error!
```

Where the array used to be inferred as `string[]`, it is now inferred as a template literal type array.

Very cool but also how many times have people mapped an array this way and pushed something which doesn't follow the pattern? Seems like one hell of a breaking change :D

## Wrap-up

All these are very interesting changes.

However, the querySelector changes do make me wonder how much worse they will be for performance. Similarly, the template literal inference feels like it will break a lot of stuff.

But we will see, either way these will be some nice changes to play around with and try out.