I have been happy working with TypeScript for quite a while now, and I am happy to say that things are moving ahead with the language quite well. It is not that long ago that TypeScript version 2.0 was realeased, and with steady releases we are now at version 2.3.
One of the big things that came to TypeScript in version 2.0 was discriminated union types and the option to do strict null checks, which combine quite nicely. Discriminated unions are simply done using the pipe operator, and the compiler will do strict null checks if you use the --strictNullChecks switch. Because you might need undefined and null in certain places, you can use these in a discriminated union, which means that you are explicit about when it is actually needed like so.
let age: number | null | undefined;
For parameters undefined is automatically added if you specify them as optional so you can still specify eg.
type Person = (age?: number) => string;
I am personally a big fan of languages that don't have null at all, so this is a nice compromise in a language where removing it entirely is not really a way to go.
Mapped types and async/await
For version 2.1 of TypeScript the big news for me was keyof, mapped types, spread, rest and downlevel await support.
KeyOf is a new keyword that simply gives you the keys of an object, and this can be used in conjunktion with mapped types, that allow you to create a new type by transforming an existing one. The release notes have this sample of how you can create a type that takes another type and makes all its properties nullable.
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PartialPerson = Partial<Person>;
In the same way you could easilly change accessiblity or types on existing types - really nice feature for scenarios where one type is received and you wish to transform it, without having to specify two complete types that are alike.
Spread allows you to easilly merge objects and add new properties
var newObj = {...foo, ...bar, z: 3, y: 4};
With rest letting you handle the properties that are not picked up when destructuring an element
let obj = { a: 1, b: 1, c: 1 };
let { c, ...obj1 } = obj;
obj1; // {a: number, b:number};
Async await support available for ES3 and above compile targets is a huge thing for me, because this is something I have wanted for more than a year. I love not having to do nested callbacks all the time, but sadly the project I have been working on needed backward compatibility, so I was stuck until now.
Mix-ins and the object type
Moving on to 2.2 another feature that I have been waiting for arrived with mix-ins. There were ways to simulate mixins before, but it requited you to create stubs and it felt horrible, and like a place where TypeScript was not a natural supertype to JavaScript. However I think the mixin support came out quite nicely, so now it looks natural and not like something you are forcing, as the sample shows.
interface IPoint {
x: number;
y: number;
}
type Constructor<T> = new(...args: any[]) => T;
function WithLocation<T extends Constructor<IPoint>>(Base: T) {
return class extends Base {
getLocation(): [number, number] {
return [this.x, this.y];
}
constructor(...args: any[]) {
super(...args);
}
}
}
class Point implements IPoint {
constructor(public x: number, public y: number) { }
}
const PointWithLocation = WithLocation(Point);
let x = new PointWithLocation(10, 12);
console.log(x.getLocation());
Oddly enough an object type has never been part of TypeScript for representing non-primitive types, but this was also added with version 2.2, so it is possible to seperate objects from value types.
In version 2.3 of TypeScript full support back to ES3 compile tartgets was added for iterators and generators, and building on this async iteration was introduced using a for..await..of construct like so.
async function f() {
for await (const x of getAsyncValues()) {
console.log(x);
}
}
Generic parameter defaults
Lastly in version 2.3 generic parameter defaults were added, making it a lot simpler to define factory functions that can work on different types, but has a natural default. The release notes has this sample of a function that can create a HTML element, defaults to a div, and can optionally take an array of child elements.
declare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(element?: T, children?: U): Container<T, U>;
This was by no means a complete list of features, but it is some of the features that I have been looking forward to the most. It shows why TypeScript is attracting more and more developers, and why the values of building on JavaScript, but respecting the language and collaborating with others has allowed it to successfully taken the place that other JavaScript supersets fought for a few years ago.