Quick Start
Compiler
TypeScript is transpiled into JavaScript using a compiler.
Installation
# install compiler
$ npm install typescript --save-dev
# run compiler
$ npx src
# configure the compiler
$ npx tsc --init
Type Assignment
There are two main TypeScript assigns a type:
- Explicit
- Implicit
let name: string = "Alice";
let name2 = "Alice";
any
any
is a type that disables type checking and effectively allows all types to be used.
let v: any = true;
v = "string";
v = 12;
unknown
unknown
is a similar, but safer alternative to any
.
TypeScript will prevent unknown
types from being used:
let w: unknown = 1;
console.log(w);
w = "Alice";
console.log(w);
console.log(typeof w);
console.log(w.slice(1,2)); // error
never
never
effectively throws an error whenever it is defined.
let x: never = true; // Error: Type 'boolean' is not assignable to type 'never'.
never
is rarely used, especially by itself, its primary use is in advanced generics.
undefined and null
undefined
and null
are types that refer to the JavaScript primitives undefined
and null
respectively.
let y: undefined = undefined;
ley z: null = null;
These types don't have much use unless
strictNullChecks
is enabled in thetsconfig.json
file.
Array
const names: string[] = [];
names.push("Alice");
names.push(1); // error
Readonly
The readonly
keyword can prevent arrays from being changed.
const names: readonly string[] = ["Alice"];
names.push("A"); // error
Type inference
TypeScript can infer the type of an array if it has values.
const nums = [1,2,3];
nums.push(4);
nums.push("A"); // error
Tuple
tuple
is a typed array with a pre-defined length and types for each index.
// define tuple
let t: [number, boolean, string];
// init
t = [5, false, "Alice"];
If you have ever used React before you have worked with tuples more than likely.
useState
returns a tuple of the value and a setter function.
const [firstName, setFirstName] = useState('Dylan')
is a common example.Because of the structure we know our first value in our list will be a certain value type in this case a
string
and the second value afunction
.
Named tuple
const graph: [x: number, y: number] = [1.0, 2.0];
Destructuring Tuples
const graph: [number, number] = [55.2, 41.3];
const [x, y] = graph;
Object
const car: {
type: string,
model: string,
year: number
} = {
type: "Toyota",
model: "Corolla",
year: 2009
};
Type inference
const car = {
type: "Toyota",
};
car.type = "Ford";
car.type = 2; // error
Optional properties
// Error: Property 'mileage' is missing in type '{ type: string; }' but required in type '{ type: string; mileage: number; }'.
const car: {
type: string,
mileage: number
} = {
type: "Toyota",
};
car.mileage = 2000;
const car: {
type: string,
mileage?: number
} = {
type: "Toyota",
};
car.mileage = 2000;
Index signature
Index signature can be used for objects without a defined list of properties.
const nameAgeMap: {[index: string]: number} = {};
nameAgeMap.Jack = 25;
nameAgeMap.Mark = "Fifty"; // error
Enum
An enum
is a special “class” that represents a group of constants.
Enums come in two flavors string
and numeric
.
Numeric enums (default)
By default, enums will initialize the first value to 0 and add 1 to each additional value:
enum CardinalDirections {
North,
East,
South,
West
}
let currentDir = CardianlDirections.North;
console.log(currentDir); // 0
currentDir = "North"; // error
You can set the value of the first numeric enum and have it auto increment from that:
enum Dirs {
North = 1,
East,
South,
West
}
console.log(Dirs.North); // 1
console.log(Dirs.East); // 2
You can assign unique number values for each enum value.
enum Dirs {
North = 100,
East = 200,
South = 300,
West = 400
}
String enums
enum CardinalDirections {
North = 'North',
East = "East",
South = "South",
West = "West"
};
// logs "North"
console.log(CardinalDirections.North);
// logs "West"
console.log(CardinalDirections.West);
Type alias and interface
Type alias
// Try creating a new Car using the alias provided
type CarYear = number;
type CarType = string;
type CarModel = string;
type Car = {
year: CarYear,
type: CarType,
model: CarModel
};
const carYear: CarYear = 2001
const carType: CarType = "Toyota"
const carModel: CarModel = "Corolla"
const car: Car = {
year: carYear,
type: carType,
model: carModel
};
console.log(car);
Interface
Interfaces are similar to type aliases, except they only apply to object
types.
// Try creating a new interface using it below
interface Rectangle {
height: number,
width: number
};
const rectangle: Rectangle = {
height: 20,
width: 10
};
console.log(rectangle);
Extending interfaces
Extending an interface means you are creating a new interface with the same properties as the original, plus something new.
// Try creating a new interface and extending it like below
interface Rectangle {
height: number,
width: number
}
interface ColoredRectangle extends Rectangle {
color: string
}
const coloredRectangle: ColoredRectangle = {
height: 20,
width: 10,
color: "red"
};
console.log(coloredRectangle);
Union Type
Union types are used when a value can be more than a single type.
function printStatusCode(code: string | number) {
console.log(`Code is ${code}.`);
}
printStatusCode(404);
printStatusCode("404");
Function
The type of the value returned by the function can be explicitly defined.
function getTime(): number {
return new Date().getTime();
}
// void return type
func printHello(): void {
console.log("hello");
}
Parameter type
function multiply(a: number, b: number) {
return a*b;
}
If no parameter type is defined, TypeScript will default to using
any
, unless additional type information is available as shown in the Default Parameters and Type Alias sections below.
Optional parameter
function add(a: number, b: number, c?: number) {
return a + b + (c || 0);
}
Default parameter
function pow(val: number, exponent: number = 10) {
return val ** exponent;
}
Named parameter
function divide({dividend, divisor}: {dividend: number, divisor: number}) {
return dividend / divisor;
}
Rest parameters
Rest parameters can be typed like normal parameters, but the type must be an array as rest parameters are always arrays.
function add(a: number, b: number, ...rest: number[]) {
return a + b + rest.reduce((p, c) => p + c, 0);
}
Type alias
type Negate = (value: number) => number;
const negateFuntion: Negate = (value) => value * -1;
Type casting
as
as
will directly change the type of the given variable.
let x: unknown = "hello";
console.log((x as string).length);
Casting doesn’t change the type of the data within the variable.
let x: unknown = 4; console.log((x as string).length); // undefined
TypeScript will still attempt to typecheck casts to prevent casts that don't seem correct.
// Error: Conversion of type 'number' to type 'string' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. console.log((4 as string).length);
<>
Using <>
works the same as casting with as
.
let x: unknown = "Hello";
console.log((<string>x).length);
This type of casting will not work with TSX, such as when working on React files.
Class
TypeScript adds types and visibility modifiers to JavaScript classes.
class Person {
name: string;
}
const p = new Person();
p.name = "Alice";
Visibility
public
- default, allows access to the class member from anywhereprivate
- only allows access to the class member from within the classprotected
- allows access to the class member from itself and any classes that inherit it, which is covered in the inheritance below
class Person {
private name: string;
public constructor(name: string) {
this.name = name;
}
public getName(): string {
return this.name;
}
}
const person = new Person("Jane");
console.log(person.getName()); // person.name isn't accessible from outside the class since it's private
Readonly
readonly
keyword can prevent class members from being changed.
class Person {
private readonly name: string;
public constructor(name: string) {
// name cannot be changed after this initial definition, which has to be either at it's declaration or in the constructor.
this.name = name;
}
public getName(): string {
return this.name;
}
}
const person = new Person("Jane");
console.log(person.getName());
Inheritance
Classes can extend each other through the extends
keyword. A class can only extends one other class.
interface Shape {
getArea: () => number;
}
class Rectangle implements Shape {
public constructor(protected readonly width: number, protected readonly height: number) {}
public getArea(): number {
return this.width * this.height;
}
}
class Square extends Rectangle {
public constructor(width: number) {
super(width, width);
}
// getArea gets inherited from Rectangle
}
const mySq = new Square(20);
console.log(mySq.getArea());
Override
When a class extends another class, it can replace the members of the parent class with the same name.
Newer versions of TypeScript allow explicitly marking this with the override
keyword.
interface Shape {
getArea: () => number;
}
class Rectangle implements Shape {
// using protected for these members allows access from classes that extend from this class, such as Square
public constructor(protected readonly width: number, protected readonly height: number) {}
public getArea(): number {
return this.width * this.height;
}
public toString(): string {
return `Rectangle[width=${this.width}, height=${this.height}]`;
}
}
class Square extends Rectangle {
public constructor(width: number) {
super(width, width);
}
// this toString replaces the toString from Rectangle
public override toString(): string {
return `Square[width=${this.width}]`;
}
}
const mySq = new Square(20);
console.log(mySq.toString());
By default the
override
keyword is optional when overriding a method, and only helps to prevent accidentally overriding a method that does not exist. Use the settingnoImplicitOverride
to force it to be used when overriding.
Abstract Classes
Classes can be written in a way that allows them to be used as a base class for other classes without having to implement all the members. This is done by using the abstract
keyword. Members that are left unimplemented also use the abstract
keyword.
abstract class Polygon {
public abstract getArea(): number;
public toString(): string {
return `Polygon[area=${this.getArea()}]`;
}
}
class Rectangle extends Polygon {
public constructor(protected readonly width: number, protected readonly height: number) {
super();
}
public getArea(): number {
return this.width * this.height;
}
}
const myRect = new Rectangle(10,20);
console.log(myRect.getArea());
Generic
Function
function createPair<S, T>(v1: S, v2: T): [S, T] {
return [v1, v2];
}
console.log(createPair<string, number>("hello", 42)); // ["hello", 42]
Class
class NamedValue<T> {
private _value: T | undefined;
constructor(private name: string) {}
public setValue(value: T) {
this._value = value;
}
public getValue(): T | undefined {
return this._value;
}
public toString(): string {
return `${this.name}: ${this._value}`;
}
}
const value = new NamedValue<number>('myNumber');
value.setValue(10);
console.log(value.toString()); // myNumber: 10
Type alias
type Wrapped<T> = { value: T };
const wrappedValue: Wrapped<number> = { value: 10 };
Extend
function createLoggedPair<S extends string | number, T extends string | number>(v1: S, v2: T): [S, T] {
console.log(`creating pair: v1='${v1}', v2='${v2}'`);
return [v1, v2];
}
Utility Type
Partial
Partial
changes all the properties in an object to be optional.
interface Point {
x: number;
y: number;
}
let pointPart: Partial<Point> = {}; // `Partial` allows x and y to be optional
pointPart.x = 10;
Required
Required
changes all the properties in an object to be required.
interface Car {
make: string;
model: string;
mileage?: number;
}
let myCar: Required<Car> = {
make: 'Ford',
model: 'Focus',
mileage: 12000 // `Required` forces mileage to be defined
};
Record
Record
is a shortcut to defining an object type with a specific key type and value type.
const nameAgeMap: Record<string, number> = {
'Alice': 21,
'Bob': 25
};
Omit
Omit
removes keys from an object type.
interface Person {
name: string;
age: number;
location?: string;
}
const bob: Omit<Person, 'age' | 'location'> = {
name: 'Bob'
// `Omit` has removed age and location from the type and they can't be defined here
};
Pick
Pick
removes all but the specified keys from an object type.
interface Person {
name: string;
age: number;
location?: string;
}
const bob: Pick<Person, 'name'> = {
name: 'Bob'
// `Pick` has only kept name, so age and location were removed from the type and they can't be defined here
};
Exclude
Exclude
removes types from a union.
type Primitive = string | number | boolean
const value: Exclude<Primitive, string> = true; // a string cannot be used here since Exclude removed it from the type.
ReturnType
ReturnType
extracts the return type of a function type.
type PointGenerator = () => { x: number; y: number; };
const point: ReturnType<PointGenerator> = {
x: 10,
y: 20
};
Parameters
Parameters
extracts the parameter types of a function type as an array.
type PointPrinter = (p: { x: number; y: number; }) => void;
const point: Parameters<PointPrinter>[0] = {
x: 10,
y: 20
};
Readonly
Readonly
is used to create a new type where all properties are readonly, meaning they cannot be modified once assigned a value.
Keep in mind TypeScript will prevent this at compile time, but in theory since it is compiled down to JavaScript you can still override a readonly property.
interface Person {
name: string;
age: number;
}
const person: Readonly = {
name: "Dylan",
age: 35,
};
person.name = 'Israel'; // prog.ts(11,8): error TS2540: Cannot assign to 'name' because it is a read-only property.