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
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
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
However, this isn't possible right now with anything more complex:
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:
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:
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:
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:
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:
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:
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.
Top comments (2)
looks good. Do you think static indexing will obviate these kinds of errors?
This comes up a lot when duck-typing custom elements at run time.
in that example it wouldn't be of much help, but IIRC there's ongoing work elsewhere to type
constructor
astypeof T
. if it was, rather thanFunction
, it would work in your example i think