TypeScript 2.0 is almost out, and today we’re happy to show just how close we are with our release candidate! If you haven’t used TypeScript yet, check out the intro tutorial on our website to get started.
To start using the RC now, you can download TypeScript 2.0 RC for Visual Studio 2015 (which requires VS Update 3), grab it through NuGet, or use npm:
npm install -g typescript@rc
Visual Studio Code users can follow the steps here to use the RC.
This RC gives an idea of what the full version of 2.0 will look like, and we’re looking for broader feedback to stabilize and make 2.0 a solid release. Overall, the RC should be stable enough for general use, and we don’t expect any major new features to be added past this point.
On the other hand, lots of stuff has been added since 2.0 beta was released, so here’s a few features that you might not have heard about since then.
Tagged Unions
Tagged unions are an exciting new feature that brings functionality from languages like F#, Swift, Rust, and others to JavaScript, while embracing the way that people write JavaScript today. This feature is also called discriminated unions, disjoint unions, or algebraic data types. But what’s in the feature is much more interesting than what’s in the name.
Let’s say you have two types: Circle
and Square
. You then have a union type of the two named Shape
.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
Notice that both Circle
and Square
have a field named kind
which has a string literal type. That means the kind
field on a Circle
will always contain the string "circle"
. Each type has a common field, but has been tagged with a unique value.
In TypeScript 1.8, writing a function to get the area of a Shape
required a type assertions for each type in Shape
.
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
// Convert from 'Shape' to 'Circle'
let c = shape as Circle;
return Math.PI * c.radius ** 2;
case "square":
// Convert from 'Shape' to 'Square'
let sq = shape as Square;
return sq.sideLength ** 2;
}
}
Notice we made up intermediate variables for shape
just to keep this a little cleaner.
In 2.0, that isn’t necessary. The language understands how to discriminate based on the kind
field, so you can cut down on the boilerplate.
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
// 'shape' is a 'Circle' here.
return Math.PI * shape.radius ** 2;
case "square":
// 'shape' is a 'Square' here.
return shape.sideLength ** 2;
}
}
This is totally valid, and TypeScript can use control flow analysis to figure out the type at each branch. In fact, you can use --noImplicitReturns
and the upcoming --strictNullChecks
feature to make sure these checks are exhaustive.
Tagged unions make it way easier to get type safety using JavaScript patterns you’d write today. For example, libraries like Redux will often use this pattern when processing actions.
More Literal Types
String literal types are a feature we showed off back in 1.8, and were tremendously useful. Like you saw above, we were able to leverage them to bring you tagged unions.
We wanted to give some more love to types other than just string
. In 2.0, each unique boolean
, number
, and enum member will have its own type!
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
let nums: Digit[] = [1, 2, 4, 8];
// Error! '16' isn't a 'Digit'!
nums.push(16);
Using tagged unions, we can express some things a little more naturally.
interface Success<T> {
success: true;
value: T;
}
interface Failure {
success: false;
reason: string;
}
type Result<T> = Success<T> | Failure;
The Result<T>
type here represents something that can potentially fail. If it succeeds, it has a value
, and if it fails, it contains a reason for failure. The value
field can only be used when success
is true
.
declare function tryGetNumUsers(): Result<number>;
let result = tryGetNumUsers();
if (result.success === true) {
// 'result' has type 'Success<number>'
console.log(`Server reported ${result.value} users`);
}
else {
// 'result' has type 'Failure'
console.error("Error fetching number of users!", result.reason);
}
You may’ve noticed that enum members get their own type too!
enum ActionType { Append, Erase }
interface AppendAction {
type: ActionType.Append;
text: string;
}
interface EraseAction {
type: ActionType.Erase;
numChars: number;
}
function updateText(currentText: string, action: AppendAction | EraseAction) {
if (action.type === ActionType.Append) {
// 'action' has type 'AppendAction'
return currentText + action.text;
}
else {
// 'action' has type 'EraseAction'
return currentText.slice(0, -action.numChars);
}
}
Globs, Includes, and Excludes
When we first introduced the tsconfig.json
file, you told us that manually listing files was a pain. TypeScript 1.6 introduced the exclude
field to alleviate this; however, the consensus has been that this was just not enough. It’s a pain to write out every single file path, and you can run into issues when you forget to exclude new files.
TypeScript 2.0 finally adds support for globs. Globs allow us to write out wildcards for paths, making them as granular as you need without being tedious to write.
You can use them in the new include
field as well as the existing exclude
field. As an example, let’s take a look at this tsconfig.json
that compiles all our code except for our tests:
{
"include": [
"./src/**/*.ts"
],
"exclude": [
"./src/tests/**"
]
}
TypeScript’s globs support the following wildcards:
*
for 0 or more non-separator characters (such as/
or\
).?
to match exactly one non-separator character.**/
for any number of subdirectories
Next Steps
Like we mentioned, TypeScript 2.0 is not far off, but using the RC along with 2.0’s new features will play a huge part in that release for the broader community.
Feel free to reach out to us about any issues through GitHub. We would love to hear any and all feedback as you try things out. Enjoy!
0 comments