TypeScript Type System

    • The type system in typescript is designed to be optional so that your javascript is typescript.
    • TypeScript does not block JavaScript emit in the presence of Type Errors, allowing you to progressively update your JS to TS.

    Now let’s start with the syntax of the TypeScript type system. This way you can start using these annotations in your code immediately and see the benefit. This will prepare you for a deeper dive later.

    As mentioned before Types are annotated using :TypeAnnotation syntax. Anything that is available in the type declaration space can be used as a Type Annotation.

    The following example demonstrates type annotations can be used for variables, function parameters and function return values:

    The JavaScript primitive types are well represented in the TypeScript type system. This means string, number, boolean as demonstrated below:

    1. var num: number;
    2. var str: string;
    3. var bool: boolean;
    4. num = 123;
    5. num = 123.456;
    6. num = '123'; // Error
    7. str = '123';
    8. str = 123; // Error
    9. bool = true;
    10. bool = false;
    11. bool = 'false'; // Error

    Arrays

    TypeScript provides dedicated type syntax for arrays to make it easier for you to annotate and document your code. The syntax is basically postfixing [] to any valid type annotation (e.g. :boolean[]). It allows you to safely do any array manipulation that you would normally do and protects you from errors like assigning a member of the wrong type. This is demonstrated below:

    1. var boolArray: boolean[];
    2. boolArray = [true, false];
    3. console.log(boolArray[0]); // true
    4. console.log(boolArray.length); // 2
    5. boolArray[1] = true;
    6. boolArray = [false, false];
    7. boolArray[0] = 'false'; // Error!
    8. boolArray = 'false'; // Error!
    9. boolArray = [true, 'false']; // Error!

    Interfaces are the core way in TypeScript to compose multiple type annotations into a single named annotation. Consider the following example:

    1. interface Name {
    2. first: string;
    3. second: string;
    4. }
    5. var name: Name;
    6. name = {
    7. first: 'John',
    8. second: 'Doe'
    9. };
    10. name = { // Error : `second` is missing
    11. first: 'John'
    12. };
    13. name = { // Error : `second` is the wrong type
    14. first: 'John',
    15. second: 1337
    16. };

    Here we’ve composed the annotations first: string + second: string into a new annotation Name that enforces the type checks on individual members. Interfaces have a lot of power in TypeScript and we will dedicate an entire section to how you can use that to your advantage.

    Inline Type Annotation

    Instead of creating a new interface you can annotate anything you want inline using :{ /*Structure*/ }. The previous example presented again with an inline type:

    1. var name: {
    2. first: string;
    3. second: string;
    4. };
    5. name = {
    6. first: 'John',
    7. second: 'Doe'
    8. };
    9. first: 'John'
    10. };
    11. name = { // Error : `second` is the wrong type
    12. first: 'John',
    13. second: 1337
    14. };

    Special Types

    Beyond the primitive types that have been covered there are few types that have special meaning in TypeScript. These are any, null, undefined, void.

    The any type holds a special place in the TypeScript type system. It gives you an escape hatch from the type system to tell the compiler to bugger off. any is compatible with any and all types in the type system. This means that anything can be assigned to it and it can be assigned to anything. This is demonstrated in the example below:

    1. var power: any;
    2. // Takes any and all types
    3. power = '123';
    4. power = 123;
    5. // Is compatible with all types
    6. var num: number;
    7. power = num;
    8. num = power;

    If you are porting JavaScript code to TypeScript, you are going to be close friends with any in the beginning. However, don’t take this friendship too seriously as it means that it is up to you to ensure the type safety. You are basically telling the compiler to not do any meaningful static analysis.

    null and undefined

    The null and undefined JavaScript literals are effectively treated by the type system the same as something of type any. These literals can be assigned to any other type. This is demonstrated in the below example:

    Use :void to signify that a function does not have a return type:

    1. function log(message): void {
    2. console.log(message);
    3. }

    Many algorithms and data structures in computer science do not depend on the actual type of the object. However you still want to enforce a constraint between various variables. A simple toy example is a function that takes a list of items and returns a reversed list of items. The constraint here is between what is passed in to the function and what is returned by the function:

    1. function reverse<T>(items: T[]): T[] {
    2. var toreturn = [];
    3. for (let i = items.length - 1; i >= 0; i--) {
    4. toreturn.push(items[i]);
    5. }
    6. return toreturn;
    7. }
    8. var sample = [1, 2, 3];
    9. var reversed = reverse(sample);
    10. console.log(reversed); // 3,2,1
    11. // Safety!
    12. reversed[0] = '1'; // Error!
    13. reversed = ['1', '2']; // Error!
    14. reversed = [1, 2]; // Okay

    Here you are basically saying that the function reverse takes an array (items: T[]) of some type T (notice the type parameter in reverse<T>) and returns an array of type T (notice : T[]). Because the reverse function returns items of the same type as it takes, TypeScript knows the reversed variable is also of type number[] and will give you Type safety. Similarly if you pass in an array of string[] to the reverse function the returned result is also an array of string[] and you get similar type safety as shown below:

    1. var strArr = ['1', '2'];
    2. var reversedStrs = reverse(strArr);
    3. reversedStrs = [1, 2]; // Error!

    In fact JavaScript arrays already have a .reverse function and TypeScript does indeed use generics to define its structure:

    1. interface Array<T> {
    2. reverse(): T[];
    3. // ...
    4. }
    1. var numArr = [1, 2];
    2. var reversedNums = numArr.reverse();
    3. reversedNums = ['1', '2']; // Error!

    We will discuss more about the Array<T> interface later when we present lib.d.ts in the section Ambient Declarations.

    Union Type

    Quite commonly in JavaScript you want to allow a property to be one of multiple types e.g. a string or a number. This is where the union type (denoted by | in a type annotation e.g. string|number) comes in handy. A common use case is a function that can take a single object or an array of the object e.g.:

    extend is a very common pattern in JavaScript where you take two objects and create a new one that has the features of both these objects. An Intersection Type allows you to use this pattern in a safe way as demonstrated below:

    1. function extend<T, U>(first: T, second: U): T & U {
    2. let result = <T & U> {};
    3. for (let id in first) {
    4. result[id] = first[id];
    5. }
    6. for (let id in second) {
    7. if (!result.hasOwnProperty(id)) {
    8. result[id] = second[id];
    9. }
    10. }
    11. return result;
    12. }
    13. var x = extend({ a: "hello" }, { b: 42 });
    14. // x now has both `a` and `b`
    15. var a = x.a;
    16. var b = x.b;

    Tuple Type

    JavaScript doesn’t have first class tuple support. People generally just use an array as a tuple. This is exactly what the TypeScript type system supports. Tuples can be annotated using :[typeofmember1, typeofmember2] etc. A tuple can have any number of members. Tuples are demonstrated in the below example:

    1. var nameNumber: [string, number];
    2. // Okay
    3. nameNumber = ['Jenny', 8675309];
    4. // Error!
    5. nameNumber = ['Jenny', '867-5309'];

    Combine this with the destructuring support in TypeScript, tuples feel fairly first class despite being arrays underneath:

    1. var nameNumber: [string, number];
    2. nameNumber = ['Jenny', 8675309];
    3. var [name, num] = nameNumber;

    TypeScript provides convenient syntax for providing names for type annotations that you would like to use in more than one place. The aliases are created using the type SomeName = someValidTypeAnnotation syntax. An example is demonstrated below:

    1. type StrOrNum = string|number;
    2. // Usage: just like any other notation
    3. var sample: StrOrNum;
    4. sample = 123;
    5. sample = '123';
    6. // Just checking
    7. sample = true; // Error!

    Unlike an interface you can give a type alias to literally any type annotation (useful for stuff like union and intersection types). Here are a few more examples to make you familiar with the syntax:

    1. type Text = string | { text: string };
    2. type Callback = (data: string) => void;

    Summary

    Now that you can start annotating most of your JavaScript code we can jump into the nitty gritty details of all the power available in the TypeScript’s Type System.