Skip to content

typescript overview

7sempra edited this page Jan 11, 2019 · 8 revisions

Typescript overview

Typescript is, essentially, Javascript plus type annotations. These types are checked at compile time, when Typescript code is compiled down to vanilla Javascript. This Javascript is then executed in Node, the browser, etc.

In addition to adding static type checking, Typescript uses a different syntax for modules, extends the syntax for declaring classes, and adds a few other minor tweaks.

Type annotations

Type annotations tell the compiler the types of your variables. They look like this:

let activeJob: Job = new Job();

and like this:

function fancify(value: number): string {   // Takes a number, returns a string
  return '#' + value + '#';
}

and like this:

class Job {
  private _id: number;
  private _name: string;
}

Inferred types

Type annotations are usually not required for the return types of functions or for variables that are initialized with a value. The compiler will infer the appropriate type:

let activeJob = new Job(); // activeJob has type `Job`

function negate(value: number) {
  return -value;  // negate() has inferred return type `number`
}

class Job {
  private _id: number;
  private _name = 'Default job name'; // _name has inferred type `string`
}

Modules

Typescript uses a different module system (ES6 modules) than the one that Node uses (require(), module.exports, etc.).

Exporting from a module

Instead of setting module.exports, use the export keyword in front of any declaration you want to be accessible by other modules:

export const BOUNCE_DURATION = 250;

export function bounceBall(ball: Ball) {
  // ...
}

export class Ball {
  // ...
}

There's special syntax for the "default" export (export default <foo>), which is intended for modules that only export a single thing, but it's use is not recommended. Just use export, even if you're only exporting one thing.

Importing other modules

There are 4 different syntaxes for importing from other modules:

// Importing from another Typescript module (two options)
import { bounceBall, Ball } from './ball';
import * as ball from './ball';

// Import an NPM module that has a typings definition
import express = require('express');

// Import an NPM module that doesn't have a typings definition
const uncheckedModule = require('uncheckedModule');

Typings for NPM modules

In order to use NPM modules in Typescript, you need to install the typing definitions for that module. It tells the compiler what the module's public interface is. Look up the module you want to use in TypeSearch and then npm install the path it gives you. Note that these type definitions are usually written by third parties and so can be fallible.

If a typing doesn't exist for your NPM module, you'll need to use Node-style imports:

const myModule = require('myModule');

Unfortunately, this means that any code that interacts with this module won't benefit from type-checking.

Strict null checks

Variables have to be explicitly marked as nullable or undefinable if it's possible for them to hold one of those values:

let count: number = 3;

count = null;      // Error: count doesn't have the 'null' type
count = undefined; // Error: count doesn't have the 'undefined' type


let nullableCount: number | null;

nullableCount = 3;
nullableCount = null; // Okay

If you try to access a possibly null value, the compiler will yell at you unless you wrap it in a null check:

function printNum(value: number | null) {
  console.log(value.toString()); // ERROR: value might be null

  if (value != null) {
    console.log(value.toString()); // Okay
  }
}

Null vs. undefined

In Typescript, null and undefined have different, incompatible types. This incompatibility can be annoying at times, such as when you have an undefinable value but the function you want to use only takes a nullable value.

There are a few options for dealing with this complexity:

  • Within a file (or group of dependent files) only use either null or undefined, but never a mixture of both. Our current code tries to do this (it uses undefined). This can be tricky, though! Tnex uses null for empty columns. At the same time, the most convenient way to specify optional parameters to functions uses undefined.
  • Use the nil type instead, which is defined in util/simpleTypes.ts. This is an alias for null | undefined. If you use it everywhere, things will generally work out.

Optional parameters

Optional parameters are generally discouraged, as they complicate the null vs. undefined issue above. If you must use them, you've got two options:

When you mark a function parameter or class member as "optional" via the ? operator, it gains the undefined type:

function doThing(foo?: number) { // foo's type is number | undefined
  // ...
}

You can achieve the same result with null using a default value, but it's a more verbose option:

function doThing(foo: number | null = null) {
  // ...
}