Typescript basics
What is it for?
- it adds types to Javascript
- statically analysable code
- IDE autocompletion support
- easy refactoring
- type correctness

What's different?
- .ts file extension instead of .js
- compiled
- needs type definitions
for using some external libraries
Create npm package
npm init
Install typescript
npm install typescript
Initialize typescript
npx tsc --init
tsconfig.json
- configuration for typescript compiler
for node use "target"="es6"
Typescript hello world
- create index.ts
- console.log('Hello world')
- run npx tsc
- run node index.js
Types
Basic types
- boolean
- number
- string
- array
- any
- unknown
- null, undefined
- void, never
How to use them?
let myNumericVariable: number = 5;
let myString: string = 'hello';
let myArray: string[] = ['hello', 'hi'];
let myArray: Array<string> = ['hello', 'hi'];
And in functions...
function convertToUppercase(input: string): string {
return input.toUpperCase();
}
function createPerson(
id: number,
firstName: string,
lastName: string
): {id: number, name: string} {
return {
id,
name: `${firstName} ${lastName}`,
};
}
Let's make a calculator
- initialize and configure typescript
- implement functions for:
- addition
- subtraction
- sum of numbers in array
Optional parameters
function add(a: number, b: number, c?: number) {
return a + b + (c ?? 0);
}
Modules
Modules in typescript
- when using multiple files
- export
- import
Named export
import { calculateSum } from './my-file';
let result: number = calculateSum([1,2,3])
Import
export function calculateSum(numbers: number[]): number {
return numbers.reduce((a, b) => a + b)
}
Default export
import renamedSum from './my-file';
let result: number = renamedSum([1,2,3]);
Import
export default function calculateSum(numbers: number[]): number {
return numbers.reduce((a, b) => a + b)
}
- only one default export per module
esModuleInterop
import * as add from './addition';
- importing CommonJS default export
esModuleInterop=true
esModuleInterop=false
import add from './addition';
Try it out
- move calculation functions to separate file (calculations.ts)
- use import/export
- create const for PI = 3.14 to a pi.ts and export it as default export
Importing libraries
import lodash from 'lodash';
lodash.without([1,2,3], 2);
- remember to install the library first!
You will also need type definitions for lodash
What is type definition?
- file containing description of all functions, classes and objects used in the library
- describes only shape, no implementation
- .d.ts extension
- https://github.com/DefinitelyTyped/DefinitelyTyped
- install it using:
- npm install @types/lodash --save-dev
➡️ Use lodash in Typescript
- initialize and configure typescript
-
npm install @types/lodash --save-dev
- create array of people with id and name
- use lodash.find to find person by id
const people = [
{
id: 1,
name: 'Peter'
},
{
id: 2,
name: 'Rachel'
},
{
id: 3,
name: 'John'
},
{
id: 4,
name: 'Tim'
},
{
id: 5,
name: 'Zed'
}
];
Person could be typed
const people = [
{
id: 1,
name: 'Peter'
},
{
id: 2,
name: 'Rachel'
},
{
id: 3,
name: 'John'
},
{
id: 4,
name: 'Tim'
},
{
id: 5,
name: 'Zed'
}
];
Types
Interfaces
Person interface
interface Person {
id: number;
name: string;
}
Interface
- describes the shape of object / function / class
- no implementation
- not included in transpiled code
interface Person {
id: number;
name: string;
age?: number;
greet(name: string): string;
}
function createPerson(id: number, firstName: string, lastName: string)
: {id: number, name: string} {
return {
id,
name: `${firstName} ${lastName}`,
};
}
// becomes this:
interface Person {
id: number;
name: string;
}
function createPerson(id: number, firstName: string, lastName: string): Person {
return {
id,
name: `${firstName} ${lastName}`,
};
}
Use Person as return type
Dynamic keys
const people = {
martin: {
name: 'Martin',
age: 32
},
homer: {
name: 'Homer',
age: 50
}
}
console.log(item.martin.name)
console.log(item['martin'].name)
Dynamic keys
interface Person {
name: string;
age: number;
}
interface PeopleByName {
[key: string]: Person
}
let people: PeopleByName = {
martin: {
name: 'Martin',
age: 32
},
homer: {
name: 'Homer',
age: 50
}
}
console.log(item.martin.name)
console.log(item['martin'].name)
Interface for a function
interface PrintFunction {
(text: string): void;
}
let printer: PrintFunction;
printer = (text) => console.log(text)
printer = (text) => console.error(text)
Interface can extend
interface Person {
name: string;
age: number;
}
// Extending an interface
interface Employee extends Person {
employeeId: number;
}
- other interface or class
Reopening interface
interface Person {
name: string;
age: number;
}
// this extends Person
interface Person {
gender: string;
}
- multiple declarations extend
Interfaces excercise
- define interface for a calculator
- add(a, b) → number
- subtract(a, b) → number
- PI = 3.14
- create an object which fits such interface
let calculator: Calculator = {...}
Classes
- same as ES6 classes
- encapsulate behaviour
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayName() {
console.log(`My name is ${this.name}`);
}
}
Constructor properties
- defines property in the constructor
- needs access modificator
class Animal {
constructor(public name: string) {
}
sayName() {
console.log(`My name is ${this.name}`);
}
}
public / private / protected
- for both properties and methods
- limits access from outside
-
public
- is default
- everything may access the property
-
private
- accessible only from the class
-
protected
- accessible only from class and its derived classes
readonly
- readonly property must be assigned at
- in declaration
- in constructor
- can be combined with public/private/protected
Create instances
let dog = new Animal('Lassie');
let mouse = new Animal('Mickey');
dog.sayName();
mouse.sayName();
Class inheritance
- class may inherit properties and methods from another class
- parent class should be always more general
- must call super(...) - constructor of the superclass
class Dog extends Animal {
constructor(name: string) {
super(name);
}
bark() {
console.log(`Bark!`);
}
}
Method override
class Animal {
move(coordinates: Coordinates) {
this.coordinates = coordinates;
}
}
class Bird extends Animal {
isInTheAir: false;
move(coordinates: Coordinates) {
this.isInTheAir = true;
super.move(coordinates);
}
}
Method overload
class Calculator {
// Overload signatures
add(a: number, b: number): number; // Add two numbers
add(a: string, b: string): string; // Concatenate two strings
add(a: number, b: number, c: number): number; // Add three numbers
// Single method implementation
add(a: unknown, b: unknown, c?: number): any {
if (typeof a === "number" && typeof b === "number") {
if (c !== undefined) {
return a + b + c;
}
return a + b;
}
if (typeof a === "string" && typeof b === "string") {
return a + b;
}
}
}
const calc = new Calculator();
console.log(calc.add(2, 3)); // Outputs: 5
console.log(calc.add("Hello, ", "World")); // Outputs: Hello, World
console.log(calc.add(1, 2, 3)); // Outputs: 6
Static methods
- belong to the class itself, rather than instances
- 👉 don't need an instance
- 👉 can access only static properties/methods
- useful for utility classes or constants
class MathUtil {
static PI: number = 3.14159;
static calculateCircumference(radius: number): number {
return 2 * MathUtil.PI * radius;
}
}
console.log(MathUtil.PI);
MathUtil.calculateCircumference(3);
Abstract class
- cannot be instantiated
- subclass has to fill missing (abstract) pieces
abstract class Shape {
abstract area(): number;
describe(): void {
console.log("This is a shape");
}
}
class Circle extends Shape {
constructor(public radius: number) {
super();
}
area(): number {
return Math.PI * this.radius * this.radius;
}
}
Let's describe an army
- soldier with sword and shield
- archer with a bow
- knight on a horse with sword
- everyone has some coordinates [X, Y]
- abilities
- use weapons
- move
Types
Type
- you can define your own types
- similar to interface
- interface describes object-like stuff
- type can use also primitive types
type Person = {
name: string;
age: number;
}
type Answer = 'yes' | 'no';
let x: Answer;
Type
- works with primitive types
- more flexible (union etc)
- better for combining types
- sometimes don't appear in error messages
Interface
- redefinition extends
- for object types
- always appear with the original name in error messages
- familiar in object-oriented codebases
Type a function
type ToStringFn = (x: number) => string;
const toString: ToStringFn = (x: number) => `${x}`;
Union type
let value: number | string = 5; // ok
value = 'hi'; // also ok
let pet: Dog | Turtle | Cat;
Type guards
- tells the compiler true type
type Square = {
type: 'square';
size: number;
};
type Circle = {
type: 'circle';
radius: number;
};
type Shape = Square | Circle;
const shape: Shape = await fetch('https://server');
if (isSquare(shape)) {
console.log(`Size is ${shape.size}`);
}
function isSquare(shape: Shape): shape is Square {
return shape.type === 'square';
}
"in"
- limit object key
type Answer = 'yes' | 'no';
type AnswerList = {
[key in Answer]: boolean;
}
const list: AnswerList = {
yes: true,
no: false
}
typeof
- get type from data
const obj = {
id: 1,
name: 'Martin'
}
type Person = typeof obj;
const obj2: Person = {
id: 2,
name: 'Lukas'
}
keyof
- gets keys from an object type
type Person = {
id: number;
name: string;
age: number;
};
// 'id' | 'name' | 'age'
const key: keyof Person = 'id';
Intersection type
- combines multiple types into one
type Person = {
id: number;
name: string
}
type Developer = {
os: 'Linux' | 'macOS' | 'Windows';
}
const someone: Person & Developer = {
id: 5,
name: 'George',
os: 'Linux'
}
Generics
let value: Array<number> = [5, 2, 4];
// generic function
function print<T>(item: T) {
console.log(item);
return item;
}
print<string>('hi');
let x = print(4); // x will be of type number
Generic class
class Bucket<T> {
items: T[] = [];
insert(item: T) {
this.items.push(item);
}
}
let myBucket = new Bucket<string>();
myBucket.insert('hello');
➡️ Create a generic map
- store data under keys
- methods
- set(key, value)
- get(key)
- has(key)
- entities() - gives all keys
- print() - prints everything inside the map
Extract type from object type
type Person = {
name: string,
age: number;
}
type Age = Person["age"];
Utility classes
Record<key, value>
const obj: Record<string, number> = {
one: 1,
two: 2,
three: 3
};
type Person = {
name: string
};
const obj: Record<number, Person> = {
1: { name: 'Martin'},
2: { name: 'Lukas'},
};
- for objects
Partial<>
interface Person {
age: number;
name: string;
}
let obj: Partial<Person> = {
age: 32
}
- makes all keys optional
Pick<>
interface Person {
age: number;
name: string;
height: number;
}
let obj: Pick<Person, 'age' | 'name'> = {
age: 32,
name: 'Martin'
}
- object with only certain keys
Omit<>
interface Person {
age: number;
name: string;
height: number;
}
let obj: Omit<Person, 'height'> = {
age: 32,
name: 'Martin'
}
- object without certain keys
Exclude<>
type Animal = Dog | Turtle | Shark;
let obj: Exclude<Animal, Turtle>
// obj: Dog | Shark
- disallows certain type from union
Parameters<>
type AdditionFn = (a: number, b: number) => number;
type Params = Parameters<AdditionFn>; // [number, number]
function add(a: number, b: number): number {
return a + b;
}
type Params = Parameters<typeof add>; // [number, number]
- for function parameters
Conditional type
type Coords3d = {
x: number;
y: number;
z: number;
};
type Coords2d = {
x: number;
y: number;
};
type Sphere = {
coords: Coords3d;
};
type Circle = {
coords: Coords2d;
};
type Shape = Sphere | Circle;
function placeRandomly<T extends Shape>(
shape: T
): T extends Sphere ? Coords3d : Coords2d {
...
}
placeRandomly({ coords: { x: 1, y: 2 } });
1️⃣ Type a function
- function removes age from an object
- use Omit<>
- inputs:
- object
- output
- object without age
function removeAge(obj) {
const { age, ...duplicateWithoutAge} = obj;
return duplicateWithoutAge;
}
2️⃣ [hard] Type a function
- for a function which removes key from object
- inputs:
- object
- key to be removed
- output
- object without the key
function removeKey(obj, key) {
const duplicate = { ...obj };
delete duplicate[key];
return duplicate;
}
Cheatsheet
Enums
enum Status {
Success = 'SUCCESS',
Error = 'ERROR',
}
let requestStatus: Status = Status.Success;
Typing this
Enforce specific context
type Person = {
name: string;
age: number;
greet(this: Person): void;
};
Enforce specific context
function sayHello(this: Person) {
console.log(`Hello, my name is ${this.name}`);
}
const person = { name: "John", age: 30 };
sayHello.call(person); // Hello, my name is John
🎉
Typescript basics
By Martin Nuc
Typescript basics
- 137