Typescript

Problems

if ("" == 0) {
// It is! But why??
}

if (1 < x < 3) {
// True for *any* value of x!
}

JavaScript’s equality operator (==) coerces its arguments, leading to unexpected behavior:


const obj = { width: 10, height: 15 };
// Why is this NaN? Spelling is hard!
const area = obj.width * obj.heigth;

JavaScript also allows accessing properties which aren’t present:

TypeScript: A Static Type Checker


const obj = { width: 10, height: 15 };
const area = obj.width * obj.heigth;

// Error
Property 'heigth' does not exist on type '{ width: number; height: number; }'.
Did you mean 'height'?

JavaScript Error in runtime


const obj = { width: 10, height: 15 };
const area = obj.width * obj.heigth;

TypeScript Error in compiling time


console.log(4 / []);

// Error
The right-hand side of an arithmetic operation
must be of type 'any', 'number', 'bigint' or an enum type.

JavaScript Error in runtime


console.log(4 / []); -> Infinity

TypeScript Error in compiling time

How to add/try

Base types

Boolean

The most basic datatype is the simple true/false value, which JavaScript and TypeScript call a boolean value.

let isDone: boolean = false;

Number

As in JavaScript, all numbers in TypeScript are either floating point values or BigIntegers. These floating point numbers get the type number, while BigIntegers get the type bigint. In addition to hexadecimal and decimal literals, TypeScript also supports binary and octal literals introduced in ECMAScript 2015.

let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;


let big: bigint = 100n;

String

Another fundamental part of creating programs in JavaScript for webpages and servers alike is working with textual data. As in other languages, we use the type string to refer to these textual datatypes. Just like JavaScript, TypeScript also uses double quotes (") or single quotes (') to surround string data.

let color: string = "blue";
color = 'red';

// You can also use template strings

let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}.

sentence = I'll be ${age + 1} years old next month.`;

Array

TypeScript, like JavaScript, allows you to work with arrays of values. Array types can be written in one of two ways.

In the first, you use the type of the elements followed by [] to denote an array of that element type:

let list: number[] = [1, 2, 3];

The second way uses a generic array type, Array<elemType>:

let list: Array<number> = [1, 2, 3];

Tuple

Tuple types allow you to express an array with a fixed number of elements whose types are known, but need not be the same.

For example, you may want to represent a value as a pair of a
string and a number:

// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error

//ERROR
//Type 'number' is not assignable to type 'string'.
// Type 'string' is not assignable to type 'number'

When accessing an element with a known index, the correct type is retrieved:

// OK
console.log(x[0].substring(1));
//Error
console.log(x[1].substring(1));
// ERROR
// Property 'substring' does not exist on type 'number'.

Accessing an element outside the set of known indices fails with an error:

x[3] = "world";
// ERROR
// Tuple type '[string, number]' of length '2' has no element at index '3'.

console.log(x[5].toString());
// ERROR
// Object is possibly 'undefined'.
// Tuple type '[string, number]' of length '2' has no element at index '5'.

Unknown

We may need to describe the type of variables that we do not know when we are writing an application. These values may come from dynamic content – e.g. from the user – or we may want to intentionally accept all values in our API. In these cases, we want to provide a type that tells the compiler and future readers that this variable could be anything, so we give it the unknown type.

let notSure: unknown = 4;
notSure = "maybe a string instead";

// OK, definitely a boolean
notSure = false;

Unknown

declare const maybe: unknown;
// 'maybe' could be a string, object, boolean, undefined, or other types
const aNumber: number = maybe;
// ERROR
// Type 'unknown' is not assignable to type 'number'.

if (maybe === true) {
  // TypeScript knows that maybe is a boolean now
  const aBoolean: boolean = maybe;
  // So, it cannot be a string
  const aString: string = maybe;
// ERROR
// Type 'boolean' is not assignable to type 'string'.
}

if (typeof maybe === "string") {
  // TypeScript knows that maybe is a string
  const aString: string = maybe;
  // So, it cannot be a boolean
  const aBoolean: boolean = maybe;
// ERROR
// Type 'string' is not assignable to type 'boolean'.
}

Any

In some situations, not all type information is available or its declaration would take an inappropriate amount of effort. These may occur for values from code that has been written without TypeScript or a 3rd party library. In these cases, we might want to opt-out of type checking. To do so, we label these values with the any type:

declare function getValue(key: string): any;
// OK, return value of 'getValue' is not checked
const str: string = getValue("myString");

Any vs Unknown

Unlike unknown, variables of type any allow you to access arbitrary properties, even ones that don’t exist. These properties include functions and TypeScript will not check their existence or type:

let looselyTyped: any = 4;
// OK, ifItExists might exist at runtime
looselyTyped.ifItExists();
// OK, toFixed exists (but the compiler doesn't check)
looselyTyped.toFixed();

let strictlyTyped: unknown = 4;
strictlyTyped.toFixed();
// ERROR
// Object is of type 'unknown'.

Void

You may commonly see this as the return type of functions that do not return a value:

function warnUser(): void {
  console.log("This is my warning message");
}

Null and Undefined

// Not much else we can assign to these variables!

let u: undefined = undefined;
let n: null = null;

Never

// Function returning never must not have a reachable end point
function error(message: string): never {
  throw new Error(message);
}

// Inferred return type is never
function fail() {
  return error("Something failed");
}

// Function returning never must not have a reachable end point
function infiniteLoop(): never {
  while (true) {}
}

The never type represents the type of values that never occur. For instance, never is the return type for a function expression or an arrow function expression that always throws an exception or one that never returns. Variables also acquire the type never when narrowed by any type guards that can never be true.

Interfaces

function printLabel(labeledObj: { label: string }) {
  console.log(labeledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
interface LabeledValue {
  label: string;
}

function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
interface SquareConfig {
  color?: string;
  width?: number;
}

Optional Properties

interface Point {
  readonly x: number;
  readonly y: number;
}

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
// ERROR
// Cannot assign to 'x' because it is a read-only property.

Readonly properties

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

interface Square extends Shape, PenStroke {
  sideLength: number;
}

let square = {} as Square;
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

Extending Interfaces

Functions

function add(x: number, y: number): number {
  return x + y;
}

let myAdd = function (x: number, y: number): number {
  return x + y;
};
function buildName(firstName: string, lastName: string = "Smith") {
  return firstName + " " + lastName;
}

let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result4 = buildName("Bob", "Adams"); // ah, just right

Default value

function buildName(firstName: string, lastName?: string) {
  if (lastName) return firstName + " " + lastName;
  else return firstName;
}

let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
Expected 1-2 arguments, but got 3.
let result3 = buildName("Bob", "Adams"); // ah, just right

Optional value

Literal Types

String Literal Types

type Easing = "ease-in" | "ease-out" | "ease-in-out";

Numeric Literal Types

type Size = 1 | 2 | 3 | 4 | 5 | 6;
interface ValidationSuccess {
  isValid: true;
  reason: null;
};

interface ValidationFailure {
  isValid: false;
  reason: string;
};

type ValidationResult =
  | ValidationSuccess
  | ValidationFailure;

Advance Types

  • Generics
  • Keyof Type Operator
  • Typeof Type Operator
  • Indexed Access Types
  • Conditional Types
  • Mapped Types
  • Template Literal Types

Generic

function identity(arg: number): number {
  return arg;
}
function identity(arg: any): any {
  return arg;
}
function identity<Type>(arg: Type): Type {
  return arg;
}

let output = identity<string>("myString");

Keyof

type Point = { x: number; y: number };
type P = keyof Point;

The keyof operator takes an object type and produces a string or numeric literal union of its keys. The following type P is the same type as “x” | “y”:

Typeof

let s = "hello";
let n: typeof s;
// let n: string

TypeScript adds a typeof operator you can use in a type context to refer to the type of a variable or property:

ReturnType

function f() {
  return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;

Indexed Access Types 

type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];

We can use an indexed access type to look up a specific property on another type:

Conditional Types 

interface Animal {
  live(): void;
}
interface Dog extends Animal {
  woof(): void;
}
 
type Example1 = Dog extends Animal ? number : string;
        
// type Example1 = number
 
type Example2 = RegExp extends Animal ? number : string;

// type Example1 = string

Mapped Types

type OnlyBoolsAndNumbers = {
  [key: string]: boolean | number;
};
 
const conforms: OnlyBoolsAndNumbers = {
  del: true,
  rodney: false,
  first: 100,
};

When you don’t want to repeat yourself, sometimes a type needs to be based on another type.

Mapped Types


type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};


type CreateImmutable<Type> = {
  readonly [Key in keyof Type]: Type[Key]
}

Template Literal Types

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
 
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;

// type AllLocaleIDs = "welcome_email_id" | "email_heading_id"
//                   | "footer_title_id" | "footer_sendoff_id"
type Getters<Type> = {
    [Property in keyof Type as `get${Capitalize<string & Property>}`]:
         () => Type[Property]
};
 
interface Person {
    name: string;
    age: number;
    location: string;
}
 
type LazyPerson = Getters<Person>;

type LazyPerson = {
    getName: () => string;
    getAge: () => number;
    getLocation: () => string;
}

Class

implements

interface Pingable {
  ping(): void;
}
 
class Sonar implements Pingable {
  ping() {
    console.log("ping!");
  }
}
 
class Ball implements Pingable {
// Class 'Ball' incorrectly implements interface 'Pingable'.
//   Property 'ping' is missing in type 'Ball' but required in type 'Pingable'.
  pong() {
    console.log("pong!");
  }
}

private/protected/public

  • The private modifier allows access within the same class.
  • The protected modifier allows access within the same class and subclasses.
  • The public modifier allows access from any location.

Parameter Properties

class Params {
  constructor(
    public readonly x: number,
    protected y: number,
    private z: number
  ) {
    // No body necessary
  }
}
const a = new Params(1, 2, 3);
console.log(a.x);

Abstract

An abstract method or abstract field is one that hasn’t had an implementation provided. These members must exist inside an abstract class, which cannot be directly instantiated.

The role of abstract classes is to serve as a base class for subclasses which do implement all the abstract members. When a class doesn’t have any abstract members, it is said to be concrete.

abstract class Base {
  abstract getName(): string;
 
  printName() {
    console.log("Hello, " + this.getName());
  }
}
 
// error
const b = new Base();
class Derived extends Base {
  getName() {
    return "world";
  }
}
 
const d = new Derived();
d.printName();
Made with Slides.com