I’ve been playing with TypeScript off and on for a few months. A colleague of mine once said “JavaScript has won first place in the contest of languages no one wants to write but everyone wants to transpile to” and I agree. I find myself productive and happy when I quickly prototype in JavaScript as I appreciate how flexible the language is. But, I don’t enjoy shipping JavaScript.

I have been exploring Dart 2.0, GopherJS, Elm, and other options; however, I have decided to be professionally proficient in TypeScript. Becoming professionally proficient with any tool demands a serious investment and I’ve done enough research to feel comfortable that I’ll be happier shipping TypeScript. Furthermore, I expect there to be more opportunity for TypeScript than Dart or Elm. I already am professionally proficient with Go but the bundle size of GopherJS exceeds the typical requirements I have.

Time to learn TypeScript!


Change Log


What is TypeScript

TypeScript is an abstraction to JavaScript which performs many checks against your code and compiles down to JavaScript which is then shipped to browsers or run on the server using Node.js. TypeScript is an open source project developed and maintained by Microsoft and was first made public in 2012 after two years of private development within the halls of Microsoft. Deep in those halls, Anders Hejlsberg was leading the development of TypeScript. Anders is often called the “Father of C#” and an original author of Tubro Pascal and the chief architect of Delphi.

Why TypeScript

  • TypeScript lets you use the latest JavaScript features today and ship JavaScript compatible with older browsers (similar to Babel).
  • TypeScript lets you use features unavailable (yet) in the JavaScript specification (similar to Babel).
  • TypeScript provides strong types which help you avoid surprises at runtime (e.g., bugs) and avoid writing some code to check the typeof a value.
  • Many of the options I have considered provide these features; however, TypeScript has tremendous momentum in the community.
  • The TypeScript playground is pretty slick (ok, not a serious reason to learn but still cool).

TypeScript Basics

TypeScript’s basic type system extends the JavaScript basic type system by adding Enums. The following examples demonstrate how to use the type system.

Implicit Type Assignments (Type Inference)

TypeScript can infer the type when a value is a constant. For example, TypeScript understands that the isOK variable is a boolean and 24 is not a boolean and therefore can not be assigned to isOK.

1
2
3
4
5
let isOK = true; // Happy
isOK = false;    // HAPPY

let age = 24;    // HAPPY
isOK = age;      // SAD - Type 'number' is not assignable to type 'boolean'.

Explicit Type Assignments

Programmers can specificity specify a type in TypeScript:

1
2
3
4
let isOK: boolean = true;
isOK = false;

let age: number = 24;

Arrays

The following two statements yield the same outcome.

1
2
3
4
5
6
let implicit = ['John', 'Jenny', 'Jessica'];

let explicit: string[] = [];
explicit.push('John');
explicit.push('Jenny');
explicit.push('Jessica');

The explicit example above is transpiled into the following JavaScript which is free of the type information.

1
2
3
4
5
var namesExplicit = [];
namesExplicit.push('John');
namesExplicit.push('Jenny');
namesExplicit.push('Jessica');

In this example, the any type illustrates how you can create an escape hatch and throw type safety out of the window.

1
2
// escape hatch
let mixed: any[] = ['John', 42, true, 17.9];

Tuples

Tuples are arrays of a fixed length with type information encoded for each index. Tuples can be helpful if you know you need an array with multiple types that will be of a fixed length.

1
2
// tuples are fixed length arrays with types identified for each item in the array
let mixed: [string, number, boolean, number] = ['John', 42, true, 17.9];

Functions

The following example declares both parameter and return types for functions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// greet accepts string as the only argument and returns a string
// We inform the compiler that the `name` parameter is a string by specifying
// a parameter type.
function greet(name: string): string {
    return `Hello, ${name}`;
}

// log accepts a string as the only parameter, calls `greet` and
// logs the output. Nothing is returned which is signaled using the `void`
// return type.
function log(name: string): void {
    console.log(greet("Aaron"));
}

log("Aaron");

Specifying a function which never returns:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// loopForever will loop forever and we can inform the compiler by using the
// `never` return type.
function loopForever(): never {
    while(true) {
        console.log("Why is my CPU causing global warming?");
    }
}

// throwError is a more practical example of using never. This function
// may be called when a fatal error has occurred.
function throwError(message: string): never {
    throw new Error(message);
}

Functions as Types

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// fn will have an implicit type of "function" without
// constraining to any specific function signature.
let fn;

function returnNumber(): number {
    return 2;
}

function returnString(): string {
    return "hello";
}

fn = returnNumber();
console.log(fn()); // output is 2

fn = returnString();
console.log(fn()); // output is "hello".

// The above examples don't offer much guidance and allow for runtime surprises.
// We can "fix" this problem by defining a function signature.
let fn2: () => number;
fn2 = returnNumber; // HAPPY
fn2 = returnString; // SAD - Accepted by the compiler but undesired logic

The syntax let fn2: () => number may be confusing for those transitioning from JavaScript as it looks like an assignment of a function. The : identifies that the following characters set a type. An assignment will have an = and look something like let fn2 = () => 2; or let fn2 = () => x; where the variable X is another variable of a number type.

Objects

Implicit Object Typing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// here, we establish values as we create an object literal which
// allow the compiler to infer types:
let person = {
    name: "Aaron",
    age: 19,
}

// next, we find we are given protection from violating the
// implicit types discovered by the compiler:
person = {
    age: "Aaron",  // SAD - Type 'string' is not assignable to type 'number'.
    hasHair: false // SAD - Object literal may only specify known properties.
}

Explicit Object Typing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// here, we specifically inform the compiler of the types for each key
// and then create an object literal:
let person: {name: string, age: number} = {
    name: "Aaron",
    age: 19,
}

// as with the previous example of implicit types we are given
// protection from establishing a value with the incorrect type
// or from adding an unknown key to the object:
person = {
    age: "Aaron",  // SAD - Type 'string' is not assignable to type 'number'.
    hasHair: false // SAD - Object literal may only specify known properties.
}

Complex Object Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Here, we define a object with several keys of different types and then
// we establish a object literal:
const programmer: {
    firstName: string,
    lastName: string,
    languages: string[],
    points: (a: number, b: number) => number,
} = {
    firstName: "Aaron",
    lastName: "Greenlee",
    languages: ["Go", "JavaScript", "TypeScript"],
    points: (a: number, b: number): number => {
        return a + b
    },
};

The same outcome can be delivered using Type Aliases which are preferred if the type needs to be used several times. If we need to establish multiple programmers we probably want to define a Type Alias.

Type Aliases

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Programmer strongly types our concept of a programmer.
type Programmer = {
    firstName: string,
    lastName: string,
    languages: string[],
    points: (a: number, b: number) => number,
};

// Type definition for the win!
const aaron = {
    firstName: "Aaron",
    lastName: "Greenlee",
    languages: ["Go", "JavaScript", "TypeScript"],
    points: (a: number, b: number): number => {
        return a + b
    },
};

// Using the type definition saved us a lot of tying.
const erin = {
    firstName: "Erin",
    lastName: "Becket",
    languages: ["Haskell", "Elm", "Java", "Ballerina"],
    points: (a: number, b: number): number => {
        return a + b + 1000;
    },
}

Union Types

Union types bring back the flexibility of JavaScript without using any which completely ejects from the compilers safety checks. If your solution requires that a variable could both a string and a number you can provide this flexibility while still preventing a boolean from being accepted with Union Types.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// age can be a string or a number which is fun! Of course, there is a performance 
// penalty at runtime which is one reason I prefer static languages, but hey! this is
// all about TypeScript!
let age: number | string;

// logAge accepts a number or a string and logs it to the console.
function logAge(age: number | string): void {
    console.log(`The age provided is ${age}`);
}

// logAndReturnAge accepts a number or a string and returns it to the caller.
// Here we can see that the function signature uses Union Types for both the
// `age` parameter and the return value.
function logAndReturnAge(age: number | string): number | string {
    console.log(`The age we return is is ${age}`);
    return age;
}

This post will continue to be updated as I learn more. My plan is to learn and share the following:

  • TypeScript Classes
  • TypeScript Namespaces
  • TypeScript Interfaces
  • TypeScript Generics
  • TypeScript Type Decorators
  • TypeScript Enums

Sharing these notes helps me learn and I hope the post is helpful for you as well. Thanks for reading.