Typescript

What is it?

  • A language by Microsoft
  • It is very popular
  • It is JavaScript with type annotations
  • Having code with type annotations means that you can head off type-related errors early.
  • This is known as static type checking
  • Typescript is a superset of JavaScript, which means you can put JS in a TS file and it will run fine
  • If you write TS in a file you can't run that file directly BUT there is a compiler that transforms it to JavaScript, and it's that file that you include
  • Let's install it and see more...

Setup

Installation

The compiler

  • On your command line, type tsc -v
    • ​This will show if you have it installed
  • Create a folder in vs code
  • Create a new file called 'index.ts' and put a console log statement in in normal JS
    • .ts is the file extension for typescript files
  • With the command line pointer in that folder, type tsc index (you don't need the file extension)
  • You'll see an index.js file appear in the directory. It will be empty, but...
  • Good! Your compiler is running BUT you won't understand it's use until we write some. So let's do that...

Starting a typescript project

  • Like many things (git, npm, etc.) a typescript project needs to be initiated
  • tsc --init
  • produces a config file tsconfig.json
  • The config file has a lot of settings, which you can read about here, but are explained to you in the file
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "sourceMap": true,
    "lib": ["ESNext", "DOM"],
    "moduleResolution": "node",
    "outDir": "js",
    "noImplicitAny": true
  }
}

VSCode Build Task

  • IN SETUPS LIKE CReactAPP you won't need this step
  • 'cmd/ctrl + shift + p' then type tasks and select 'Tasks: Configure Default Build Step', 'tsc:watch - tsconfig.json'
    • this will produce a .vscode folder with tasks.json
    • Now your files are watched for changes...
{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "typescript",
      "tsconfig": "tsconfig.json",
      "option": "watch",
      "problemMatcher": [
        "$tsc-watch"
      ],
      "group": {
        "kind": "build",
        "isDefault": true
      }
    }
  ]
}

cmd/ctrl + shift + b' to run

function greet(name:string):string {
  return `Hello ${name}`;
}

greet('James');

index.ts

  • run tsc index.ts
function greet(name) {
    return "Hello " + name;
}
greet('James');

index.js

So what does that get us?

  • If we do: let funds = prompt('Cash? £10?');
  • then we do funds.toFixed(2)
    • #problem: funds is a string, not a number
  • So in typescript, we do: let funds: string = prompt('Cash? £10?');
    • entering the type of the variable with a : <type> after the name
  • then, when we do funds.toFixed(2) we get an error!!
  • If our CI is good we can use that error to stop this code entering the code base
  • A bonus here is that this brings more intellisense to your editor, as it now knows what type something is...

Sidenote: Usage with eslint

The Language

Basic Types

  • primitives: number, string, boolean, symbol, null, or undefined
  • Object family: object, array, and...
    • tuple (an array with a fixed number of elements whose types are known, but need not be the same)
    • enum (a way of giving more friendly names to sets of numeric values)

Abstract Types

  • Top Types:
    • any - all start off as any unless cast (Avoid any were possible!! Anys will break well-typed code!!)
    • unknown - like any but you can't do any.<something>
  • Bottom Types:
    • void (just nothing)
    • never (represents the type of values that can never occur)
type Vehicle = Truck | Car
 
let myVehicle: Vehicle = obtainRandomVehicle()
 
// The exhaustive conditional
if (myVehicle instanceof Truck) {
  myVehicle.tow() // Truck
} else if (myVehicle instanceof Car) {
  myVehicle.drive() // Car
} else {
  // NEITHER!
  const neverValue: never = myVehicle
}

Type Annotations

Implicit Type Annotations

let str = 'hello';
str = 2; // ERROR
  • Even without doing anything, typescript will make intelligent guesses about which types you are using
  • This applies to variables, functions, etc. - all places where you might put a type annotation

Explicit Type Annotations

// Variables
let str: string;
str = 'hello'; // fine
str = 2; // ERROR
// Functions
function logPerson(name: string, age: number): void {
  console.log(`My name is ${name} and I am ${age} years old.`);
}

logPerson('James', 42); // fine
logPerson() // Errors (if struct null typing inforced)

Variables:

On Functions:

  • There is a type after each parameter
  • There is a colon then a type before the function braces
    • This is the type that that the function returns

Union Types

let str: string | number;
str = 'hello'; // fine
str = 2; // fine
str = true; // ERROR

The variable must hold one OR another type

You can have as many as you like

type MouseEvent = 'click' | 'dblclick' | 'mouseup' | 'mousedown';

String Literal Types

type headsOrTailsResult = 'heads' | 'tails';

function flipCoin():headsOrTailsResult{
  return Math.random() > 0.5 ? 'heads' : 'tails';
}

const result = flipCoin();

The variable must hold one OR another string

Intersection Types

function getPetInsurance(pet: Animal & { chipId: string }):Insurance {
  // Code here will deal with animal instances 
  // AND enhanced animal instances with chipIds
}

The variable can hold type A AND type B (rarely used)

You can only use the properties that both type A and type B possess

Type Casting

let nameInput = document.querySelector('#name') as HTMLInputElement;

When we change something's type:

let enteredText = (nameInput as HTMLInputElement).value;
let nameInput = document.querySelector('#name');
nameInput.value; // throws an error

We can use the as keyword:

or the <> operator:

let nameInput = <HTMLInputElement>document.querySelector('#name');

Type Assertions

let nameInput = getVAT(200, 0.2, true) as string;

When we tell typescript what type an expression returns:

function getVAT(price:number, rate:number, format:boolean=false): number | string {
  let netPrice = price * rate;
  return format ? `£${netPrice}` : netPrice;
}

We can use the as keyword:

or the <> operator:

let nameInput = <string>getVAT(200, 0.2, true);
let nameInput = getVAT(200, 0.2, false) as number;

Narrowing & Type Guards

function padLeft(padding: number | string, input: string) {
  
  if (typeof padding === "number") { // <-- Type guard
    return " ".repeat(padding) + input;
  }
  
  return padding + input;
}

When we use defensive checks to ensure a type

Object Family Types

Object Types

type car = {
  name: string
  bhp: number
  avatar_url?: string
}
  • A ? refers to an optional property
  • An ! refers to a guaranteed non-null property
    • You're telling typescript that it will be guaranteed

Arrays

let scores: number[] = [56, 34, 42];

// OR

// Generics syntax
let scores: Array<number> = [56, 34, 42];

Enums

  • enums are a way of mapping a set of friendly human-readable values to machine-friendly numbers
  • Can be problematic
enum People { 'Rod', 'Jane', 'Freddy'} // 0,1,2

enum People { 'Rod'=1, 'Jane', 'Freddy' } // 1,2,3
enum People { 'Rod'=1, 'Jane'=3, 'Freddy'=5 }

let favePersonId:People = People.Freddy;
console.log(favePersonId); // 5

let favePersonName = People[favePersonId];
console.log(favePersonName); // Freddy

tuples

  • tuples are like arrays where you specify the types
  • Error if more than the specified elements
let record: [number, string] = [2, 'James'];

let record: [number, string] = [2, 'James', false]; // Error

Interfaces

  • Interfaces allow us to define group definitions
interface Person {
  name: string,
  age: number,
  job?: string
}

let person:Person = {
  name: 'James'
}; // Error: Property 'age' is missing in type
  • Optional properties are prefixed with a question mark (?)
  • Interfaces are global in the app and can be added to later in the script
  • They are for guaranteeing a schema - so any object that matches the contract passes

Functions

// Define an interface for your object
interface GreetingSettings {
  duration: number;
  color?: string;
}

const defaults = {
  duration: 500,
  color: '#000'
};



// Then use it
 
function greet(greeting: string, options: GreetingSettings = defaults, extra?: boolean): void {
  // ...
}

greet('Hello Joe!!');
greet('Hello Wayne!!', { duration: 1000, color: '#f00'});

Use with Functions

type numericFn = (num:number) => number; // type definition

let squareFn: numericFn = (n) => n*n;

console.log(squareFn(2));

function printSquare(this: Stat) { // if 'this' is unclear
  return (this.value * this.value).toFixed(2);
}


interface Stat {
  value: number;
  square(): string; // defined on an interface
}

let myStat:Stat = {
  value: 3,
  square: printSquare
};

console.log(myStat.value);
console.log(myStat.square());

Function Type Declarations

type decs are global and hoisted

function addNumbers(a: number, b: number): number {
    return a + b;
}

function addStrings(a: string, b: string): string {
    return a + b;
}

Overloading

When types of the params determine the operation of the function

Can we union these 2 functions?

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
   return a + b;
}

Yes we can!

  • Now if the args are numbers it'll output a number; strings will output a string and anything else is an any!
  • You can do the same to methods

Classes

New Features

  • public - everyone can access
  • private - only instances of that class (like #todos=[])
  • protected - instances of that class and classes that extend it
  • readonly - get only, no setting.
  • Can be used before methods or properties
class Person {
  constructor(
    public name: string,
    public role: string,
    public age: number
  ) {}
}
 
const mike = new Person("Mike Hawthorn", "CEO", 45)

Generics

Generics

  • Generics allow us to parameterise/abstract
  • they allow us to generalise
  • they allow us to say: You're going to receive an argument of type T and then use that type in your code.
  • NOTE: WHAT type T is is not defined at this stage
  • If the generic is invoked with numbers then T will be number
  • If the generic is invoked with strings then T will be strings
  • If the generic is invoked with UserType then T will be UserType
  • You'll often see:
    • “S” because it’s describing the type of a “S”tate. 

    • T (for “T”ype)
    • E (for “E”lement)
    • K (for “K”ey)
    • V (for “V”alue)
  • Resource
function holder<T>(startingVals:Array<T>) {
  
  return {
    get():Array<T>{
      return startingVals;
    },
    add(newVal:T):void {
      startingVals.push(newVal)
    }
  }
}

const numHolder = holder([2]);

numHolder.get().toUppercase(); // <-- will error

const objHolder = holder([{ name: 'James', age: 44 }]);

objHolder.add({ name: 'Robert', }) // <-- will error
  • For numHolder, T will be number
  • For objHolder, T will be an object with a name and age field
    • You can pass a typed object (with an interface) if you want to have optional fields
  • If you have more than one type the syntax is <T, U>
const inputForm = document.forms[0]! as HTMLFormElement; // Throw an error if you don't find
const nameInput = inputForm.querySelector('input#name') as HTMLInputElement;
const title = inputForm.querySelector('#title') as HTMLInputElement; // required because ambiguous
const personDisplay = document.getElementById('person-display') as HTMLParagraphElement;

interface PersonInterface {
    name: string;
    title?: string;
}

class Person implements PersonInterface {
    constructor (public name: string, public title?: string) {}
  
    getGreeting() {
        return `Hi, my name is ${this.name}, ${this.title ? `I am the ${this.title}` : ''}.`;
    }
 }

inputForm.addEventListener('submit', (e) => {
    e.preventDefault(); 

    const person = new Person(nameInput.value, title.value);
    personDisplay.textContent = person.getGreeting();
    inputForm.reset(); 
})
  • HTML Elements and their interfaces can be included in typescript if you pass "lib": ["DOM"] in your tsconfig
  • Using the as keyword you can show the type of the element

Use with DOM

3rd Party Types

Importing Types

  • If you were working with the DOM, for example, and needed types for that (such as 'HTMLInput', etc.) you can import representations of those things as types
  • You can search here for type libraries
  • One famous one is DefinitelyTyped
  • Files that have .d.ts as a file extension are type definition files which contain definitions only (example)

Resources

Resources

Typescript

By James Sherry

Typescript

Typescript is a language created by Microsoft based on JavaScript. It includes type checking to prevent errors. This is a guide to beginner/intermediate Typescript usage.

  • 1,841