DEV Community

James Garbutt
James Garbutt

Posted on

Things to watch for in TypeScript 4.2 onwards

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:

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

const obj = new Foo();
obj['a']; // number
obj['b']; // number
Enter fullscreen mode Exit fullscreen mode

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

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

Foo['a']; // number
Foo['b']; // number
Enter fullscreen mode Exit fullscreen mode

querySelector type inference

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

document.querySelector('div'); // HTMLDivElement
Enter fullscreen mode Exit fullscreen mode

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

document.querySelector('div p'); // Element
document.querySelector('div.foo'); // Element
Enter fullscreen mode Exit fullscreen mode

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:

document.querySelector('div p'); // HTMLParagraphElement
document.querySelector('div.foo'); // HTMLDivElement
Enter fullscreen mode Exit fullscreen mode

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:

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

test('a', 'b'); // fine
test('a', 5); // error
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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:

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

class Bar extends Foo {
    override x(): number {
        return 6;
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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

declare const test: Foo;

test.b; // fine
test.a; // error
test['a']; // fine
Enter fullscreen mode Exit fullscreen mode

Comes down to personal preference, I suppose.

Inferred template literal types

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

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!
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (2)

Collapse
 
bennypowers profile image
Benny Powers 🇮🇱🇨🇦

looks good. Do you think static indexing will obviate these kinds of errors?

class Foo {
  declare static type: "a"|"b";
}

class Bar {
  static type = "b";
}

const a = new Bar();

// @ts-expect-error: you're going to have to trust me on this one
if (a.constructor.type === 'a')
  console.log('oops! a compiler error!'); 
Enter fullscreen mode Exit fullscreen mode

This comes up a lot when duck-typing custom elements at run time.

Collapse
 
43081j profile image
James Garbutt

in that example it wouldn't be of much help, but IIRC there's ongoing work elsewhere to type constructor as typeof T. if it was, rather than Function, it would work in your example i think