Typescript
is a superset of Javascript. That means, all Javascript is valid Typescript
- Provide Javascript developers with an optional type system
- Provide Javascript developers with the ability to utilize planned features from future Javascript editions against current Javascript engines
View contents
There are 4 primary types in Typescript
- Implicit
- Explicit
- Structural
- Ambient
- figure out the types of the variables if they are not explicitly defined
const age = 18; // implicitly, age is number
- specify the types of the variables
const age: number = 18;
// function
function sayHello(name: string): string {
return `Hello ${name}`;
}
// arrow function
const profile = (name: string, age: number): string => {
return `${name}'s age is ${age}`;
};
// class
class Greeter {
name: string;
constructor(name: string) {
this.name = name;
}
sayHello(): string {
return `Hello ${this.name}`;
}
}
class Fruit {}
class Mango extends Fruit {}
class GreenMango extends Mango {}
// Valid, through subtypes
const greenMangoFruit: Fruit = new GreenMango();
// Valid, through subtypes
const mango: GreenMango = new Mango();
// Valid, explicitly defined as a GreenMango
const greenMango: GreenMango = new GreenMango();
If it looks like a Duck and it quacks like a Duck. It must be a Duck.
interface Comment {
id: number;
content: string;
}
interface Reply {
id: number;
content: string;
commentId: number;
}
const comment: Comment = {
id: 1,
content: "this is a comment",
};
const reply: Reply = {
id: 2,
content: "this is a reply",
commentId: 1,
};
function postComment(comment: Comment) {
// Do something
}
// Perfect - exact match
postComment(comment);
// Ok - extra information still alright
postComment(reply);
// Error - missing information
// Type '{ id: number; }' is missing the following properties from
// type 'Comment': content
postComment({ id: 1 });
// $ is global variable
declare var $: {
(selector: string): any;
};
$(".cls").show(); // okay
$(123).show(); // Error
View contents
class Point {
// Instance variables are accessible only through instances of the class.
// From inside the class, using the this keyword gives us access to the instance variables
x: number; // instance variable
y: number;
constructor(x: number, y: number) {
// constructor
this.x = x;
this.y = y;
}
add(point: Point) {
// method
return new Point(this.x + point.x, this.y + point.x);
}
}
class Point3D extends Point {
z: number;
// static properties belong to the class themselves,
// not to instances of the class — objects.
static instancesCreated = 0; // class variable
// Readonly properties are properties that can’t be changed once they’ve been set.
// A read-only property must be initialized at their declaration or in the constructor.
readonly pointName: string;
readonly numberOfPoints: number = 5;
constructor(x: number, y: number, z: number) {
super(x, y);
this.z = z;
Point3D.instancesCreated++;
this.pointName = "readonlyPoint";
}
add(point: Point3D) {
const point2D = super.add(point);
return new Point3D(point2D.x, point2D.y, this.z + point.z);
}
}
const p = new Point3D(0, 10, 20);
console.log(Point3D.instancesCreated);
View contents
There are 3 access modifiers: public, protected and private
A method or member/attribute with a public
modifier can access through:
- an instance of the class (object)
- inside the containing class (this)
A method or member/attribute with a private
modifier can access through:
- inside the containing class (this)
A method or member/attribute with a protected
modifier can access through:
- inside the containing class and subclasses (this)
Access modifier | Access from other classes? | Access from subclasses? |
---|---|---|
public | yes | yes |
protected | no | yes |
private | no | no |
by default, the property is public if no access modifier is included
View contents
Interfaces allow us to declare the structure of classes and variables.
interface ICenter {
x: number;
y: number;
}
interface ICircle {
readonly id: string;
center: ICenter;
radius: number;
color?: string; // optional property
}
interface ICircleWithArea extends ICircle {
getArea: () => number; // or, getArea(): number
}
class Circle implements ICircleWithArea {
// Readonly properties are properties that can’t be changed once they’ve been set.
// A read-only property must be initialized at their declaration or in the constructor.
readonly counter: number = 0;
readonly id: string;
center: ICenter;
radius: number;
constructor(center: ICenter, radius: number) {
this.id = "";
this.center = center;
this.radius = radius;
}
getArea() {
return Math.PI * this.radius * this.radius;
}
}
View contents
Generics offer a way to create reusable components. Generics provide a way to make components work with any data type and not restrict to one data type.
interface Queue<T> {
data: T[];
push: (t: T) => void;
pop: () => T | undefined;
}
interface Monkey {
name: string;
color: string;
}
class MonkeyQueue implements Queue<Monkey> {
data: Monkey[];
constructor() {
this.data = [];
}
push(t: Monkey): void {
this.data.push(t);
}
pop(): Monkey | undefined {
return this.data.shift();
}
}
Generic Class
class KeyValuePair<T, U> {
private key: T;
private val: U;
setKeyValue(key: T, val: U): void {
this.key = key;
this.val = val;
}
display(): void {
console.log(`Key = ${this.key}, val = ${this.val}`);
}
}
let kvp1 = new KeyValuePair<number, string>();
kvp1.setKeyValue(1, "Steve");
kvp1.display(); //Output: Key = 1, Val = Steve
Generics Function
type Link<T> = {
value: T;
next?: Link<T>;
};
function createNode<T>(value: T): Link<T> {
return { value };
}
const createNodeArrow = <T>(value: T): Link<T> => ({ value });
const createNodeArrowAlt = <T extends unknown>(value: T): Link<T> => ({
value,
});
const node = createNode<string>("wow");
const anotherNode: Link<number> = createNode(2);
const boolNode = createNode(true);
Extending Generic
const greeting = <T extends { name: string }>(obj: T) => {};
greeting({ name: "Foyez", age: 18 });
In T, must contain { name: string }. Extra properties are also acceptable.
function func<T extends {}>(param: T) {}
func(8);
func(null); // error
func(undefined); // error
T extends {} accepts anything but null
and undefined
.
View contents
abstract
classes cannot be directly instantiated. Instead, the user must create some class that inherits from the abstract class.- abstract members cannot be directly accessed, and a child class must provide the functionality.
type ITrack = { title: string } | null;
abstract class AudioDevice {
protected isPlaying: boolean = false;
protected currentTrack: ITrack = null;
constructor() {}
play(track: ITrack): void {
this.currentTrack = track;
this.isPlaying = true;
this.handlePlayCurrentAudioTrack();
}
abstract handlePlayCurrentAudioTrack(): void;
}
class Boombox extends AudioDevice {
constructor() {
super();
}
handlePlayCurrentAudioTrack() {
// Play through the boombox speakers
}
}
Utility Types ref
View contents
Constructs a type with all properties of Type set to optional.
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: "organize desk",
description: "clear clutter",
};
const todo2 = updateTodo(todo1, {
description: "throw out trash",
});
Constructs a type consisting of all properties of Type set to required. The opposite of Partial.
interface Props {
a?: number;
b?: string;
}
const obj: Props = { a: 5 };
const obj2: Required<Props> = { a: 5 }; // Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.
Constructs a type with all properties of Type set to readonly, meaning the properties of the constructed type cannot be reassigned.
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
todo.title = "Hello"; // Cannot assign to 'title' because it is a read-only property.
Constructs an object type whose property keys are
Keys
and whose property values areType
. This utility can be used to map the properties of a type to another type.
interface CatInfo {
age: number;
breed: string;
}
type CatName = "miffy" | "boris" | "mordred";
const cats: Record<CatName, CatInfo> = {
miffy: { age: 10, breed: "Persian" },
boris: { age: 5, breed: "Maine Coon" },
mordred: { age: 16, breed: "British Shorthair" },
};
Constructs a type by picking the set of properties
Keys
(string literal or union of string literals) fromType
.
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
Constructs a type by picking all properties from
Type
and then removingKeys
(string literal or union of string literals).
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
createdAt: 1615544252770,
};
type TodoInfo = Omit<Todo, "completed" | "createdAt">;
const todoInfo: TodoInfo = {
title: "Pick up kids",
description: "Kindergarten closes at 5pm",
};
Constructs a type consisting of the return type of function Type.
const greetings = (name: string): string => `Hello, ${name}`;
type funcReturnType = ReturnType<typeof greetings>; // string
View contents
type Person = {
name: string;
age: number;
};
// Primitive
type Name = string;
// Tuple
type Data = [number, string];
// Object
type PointX = { x: number };
type PointY = { y: number };
// Union (Or - At least one required)
type IncompletePoint = PointX | PointY;
// Extends/Intersection (And - All required)
type Point = PointX & PointY;
const pX: PointX = { x: 1 };
const incompletePoint: IncompletePoint = { x: 1 };
const point: Point = { x: 1 }; // Error Property 'y' is missing
// in type '{ x: number; }' but
An enum is a way to organize a collection of related values.
enum Instrument {
Guitar,
Bass,
Keyboard,
Drums,
}
/*
enum Instrument {
Guitar = 'GUITAR',
Bass = 'BASS',
Keyboard = 'KEYBOARD',
Drums = 'DRUMS'
}
*/
let instrument = Instrument.Guitar; // or, Instrument[0]
instrument = "screwdriver"; /* Error! Type '"screwdriver"'
is not assignable to type 'Instrument'.
*/
tuple is an organized array where type of a fixed number of elements is known
let contact: [string, number] = ["Foyez", 485743];
contact = ["Ana", 842903, "extra argument"]; /* Error!
Type '[string, number, string]' is not assignable to type '[string, number]'. */
any is a type that we can used with all types.
let anything: any = "anyone";
anything = 3;
In legacy projects migrating to TypeScript, it’s not uncommon to temporarily type things as any before adding more specific types over time during refactoring.
void is the absence of having any return type.
function greet(name: string): void {
console.log(`Hello, ${name}`);
}
never indicates th values that will never occur.
The never type is used when you are sure that something is never going to occur. For example, you write a function which will not return to its end point.
type Currencies = "CAD" | "USD" | "EUR";
const getRate = (rate: Currencies) => {
if (rate === "CAD") {
return 1.3;
}
if (rate === "USD") {
return 1;
}
const neverEver: never = rate; // Type 'string' is not assignable to type 'never'.
return neverEver;
};
getRate("EUR");
type BaseCourse = {
name: string;
};
interface FreeCourse extends BaseCourse {
youtube: string;
price?: never;
}
interface PaidCourse extends BaseCourse {
price: number;
youtube?: never;
}
type Course = FreeCourse | PaidCourse;
const myCourse: Course = {
// Type '{ name: string; youtube: string; price: number; }' is not assignable to type 'PaidCourse'.
name: "Typescript",
youtube: "https://yotube.com",
price: 40,
};
source: wes bos
TypeScript 3.0 introduces the unknown type which is the type-safe counterpart of any. Anything is assignable to unknown, but unknown isn’t assignable to anything but itself and any. No operations are permitted on an unknown without first asserting or narrowing to a more specific type.
let anyValue: any;
let unknownValue: unknown;
// Anything is assignable to unknown
unknownValue = 5;
unknownValue = "s";
unknownValue = true;
// unknown isn’t assignable to anything but itself and any
let newUnknownValue: unknown = unknownValue;
anyValue = unknownValue;
let num: number = unknownValue; // Type 'unknown' is not assignable to type 'number'.
// No operations are permitted on an unknown without first asserting or narrowing to a more specific type.
anyValue();
if (typeof unknownValue === "function") {
unknownValue();
}
anyValue++;
if (typeof unknownValue === "number") {
unknownValue++;
}
anyValue.split("");
if (typeof unknownValue === "string") {
unknownValue.split("");
}
type I1 = unknown & null; // null
type I2 = unknown & string; // string
type U1 = unknown | null; // unknown
type U2 = unknown | string; // unknown
const GenreTypes: { [index: number]: string } = {
1: "Metal",
2: "Rap",
3: "Pop",
};
View contents
Used to reduce a set of potential objects down to one specific object.
Example 1:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
function area(s: Shape) {
switch(s.kind){
case 'circle': {
// s.x - Property 'x' does not exist on type '{ kind: "circle"; radius: number; }'
return Math.PI * s.radius * s.radius;
}
case 'square': {
return s.x * s.x;
}
default: {
return (s.x * s.y) / 2;
}
}
}
function height(s: Shape) {
if (s.kind === "circle") {
return 2 * s.radius;
} else {
// s.kind: "square" | "triangle"
return s.x;
}
}
Example 2:
type Engineer = {occupation: 'engineer', age: number}
type Doctor = {occupation: 'doctor', sex: 'male' | 'female'}
type Person = {name: string} & (Engineer | Doctor)
function description(person: Person) {
const {name, occupation} = person
if(occupation === 'doctor') {
return `${name} is ${person.sex}`
}
return `${name} is ${person.age}`
}
View contents
When we want to change a variable from one type to another such as unknown
to number
etc, we use Type assertion. We can either use <>
angular brackets or as
keywords to do type assertion. But as
keyword is recommended.
let str: unknown = "hello";
const len = (str as string).length;
console.log(len);
Typecasting has Runtime support, whereas type assertion has no effect on runtime. It is used by the compiler.
interface Person {
name: string;
age: number;
}
const person = {} as Person; // or const person <Person>{}
person.name = "Foyez";
View contents
Type guards allow us to narrow down the type of an object within a conditional block.
Using typeof in a conditional block, the compiler will know the type of a variable to be different.
function example(x: number | boolean) {
if (typeof x === "number") {
return x.toFixed(2);
}
return x;
}
We can conditionally rule out type possibilities by asserting if a class is or is not an instance of a particular class.
class MyResponse {
header = "header example";
result = "result example";
// ...
}
class MyError {
header = "header example";
message = "message example";
// ...
}
function example(x: MyResponse | MyError) {
function example(x: MyResponse | MyError) {
if (x instanceof MyResponse) {
console.log(x.message); // Error! Property 'message' does not exist on type 'MyRespo
console.log(x.result); // Okay
} else {
// TypeScript knows this must be MyError
console.log(x.message); // Okay
console.log(x.result); // Error! Property 'result' does not exist on type 'MyError'
}
}
}
interface Person {
name: string
age: number
}
const person: Person = {
name; 'Foyez',
age: 27
}
const checkForName = 'name' in person // true
Type Predict guide
// parameterName is Type
const isString = (text: unknown): text is string =>
typeof text === "string" || text instanceof String;
The general form of a type predicate is parameterName is Type
where the parameterName
is the name of the function parameter and Type
is the targeted type.
If the type guard function returns true, the TypeScript compiler knows that the tested variable has the type that was defined in the type predicate.
Before the type guard is called, the actual type of the variable comment is not known:
But after the call, if the code proceeds past the exception (that is the type guard returned true), compiler knows that comment is of the type string:
View contents
Suppose, you'd like to make a function that it accepts a string or a list of strings and return a string or a list of strings. There are 2 approaches to do this.
Approach 1: Updating the function signature directly
function greet(person: string | string[]): string | string[] {
if (typeof person === 'string') {
return `Hello, ${person}!`;
}
if (Array.isArray(person)) {
return person.map(name => `Hello, ${name}!`);
}
throw new Error('Unable to greet');
}
greet('World'); // 'Hello, World!'
greet(['Jane', 'Joe']); // ['Hello, Jane!', 'Hello, Joe!']
Approach 2: Define separately all the ways your function can be invoked. This approach is called function overloading. This approach should be used when the function signature is relatively complex and has multiple types involved.
// Overload signatures
function greet(person: string): string;
function greet(persons: string[]): string[];
// Implementation signature
function greet(person: unknown): unknown {
if (typeof person === 'string') {
return `Hello, ${person}!`;
}
if (Array.isArray(person)) {
return person.map(name => `Hello, ${name}!`);
}
throw new Error('Unable to greet');
}
greet('World'); // 'Hello, World!'
greet(['Jane', 'Joe']); // ['Hello, Jane!', 'Hello, Joe!']
The greet()
function has 2 overload signatures and one implementation signature.
class Greeter {
message: string;
constructor(message: string) {
this.message = message;
}
// Overload signatures
greet(person: string): string;
greet(persons: string[]): string[];
// Implementation signature
greet(person: unknown): unknown {
if (typeof person === 'string') {
return `${this.message}, ${person}!`;
}
if (Array.isArray(person)) {
return person.map(name => `${this.message}, ${name}!`);
}
throw new Error('Unable to greet');
}
}
const hi = new Greeter('Hi');
hi.greet('Angela'); // 'Hi, Angela!'
hi.greet(['Pam', 'Jim']); // ['Hi, Pam!', 'Hi, Jim!']
Function overloading Do's and Don'ts
source: A Simple Explanation of Function Overloading in TypeScript
View contents
Constrained Identity Function (CIF) guide
type OperationFn = (left: number, right: number) => number;
const createOperations = <OperationsType extends Record<string, OperationFn>>(
opts: OperationsType
) => opts;
const operations = createOperations({
"+": (left, right) => left + right,
"-": (left, right) => left - right,
"*": (left, right) => left * right,
"/": (left, right) => left / right,
});
const result = operations["-"](10, 4);
console.log(result); // 6
View contents
type ObjectLiteralType = {
first: 1;
second: 2;
};
type Result = keyof ObjectLiteralType; // Inferred Type: "first" | "second"
const k: Result = "second";
type Obj = {
1: "a";
prop: "c";
};
type Res1 = Obj[1]; // Inferred Type: "a"
const s: Res1 = "a";
type Res2 = Obj[1 | "prop"]; // Inferred Type: "a" | "c"
const s2: Res2 = "c";
type ObjVal = {
a: "A";
b: "B";
};
type Values = ObjVal[keyof ObjVal]; // Inferred Type: "A" | "B"
type A = "a" | "b";
type B = "b" | "c";
type Union = A | B; // Inferred Type: "a" | "b" | "c"
// Unions with Objects
type ObjTypeA = {
firstProp: number;
sharedProp: string;
};
type ObjTypeB = {
secondProp: boolean;
sharedProp: string;
};
// Inferred Type: { firstProp: number; secondProp: boolean; sharedProp: string }
type UnionWithObj = ObjTypeA | ObjTypeB;
const t: UnionWithObj = { firstProp: 10, secondProp: false, sharedProp: "hi" };
type A1 = "a" | "b" | "c";
type A2 = "b" | "c" | "d";
type Intersection = A1 & A2; // Inferred Type: 'b' | 'c'
// Ternaries only
type Wrap<T> = T extends { length: number } ? [T] : T;
type IsAssignableTo<A, B> = A extends B ? true : false;
// Type `123` is assignable to type `number`
type Result1 = IsAssignableTo<123, number>; // Inferred Type: true
type Result2 = IsAssignableTo<number, 123>; // Inferred Type: false
type Ex<T, U> = T extends U ? never : T;
type Ex1 = Ex<1 | 2 | 3, 2>; // Inferred Type: 1 | 3
type Ex2 = Ex<1 | "a" | 2 | "b", number>; // Inferred Type: 'a' | 'b'
type Ex3 = Ex<1 | "a" | 2 | "b", 1 | "b" | "c">; // Inferred Type: 'a' | 2
type Extra<T, U> = T extends U ? T : never;
type Extra1 = Extra<1 | "a" | 2 | "b", number>; // Inferred Type: 1 | 2
type Extra2 = Extra<1 | "a" | 2 | "b", 1 | "b">; // 1 | 'b'
type ObjLiteralType = {
john: 1;
paul: 2;
george: 3;
ringo: 4;
};
type P = Pick<ObjLiteralType, "george" | "ringo">; // Inferred Type: {george: 2; ringo: 4; }
type ObjLiteralType1 = {
john: 1;
paul: 2;
george: 3;
ringo: 4;
};
type O = Omit<ObjLiteralType1, "george" | "ringo">; // Inferred Type: {john: 1; paul: 2; }
function func<T extends {}>(param: T) {}
func(8);
func(null); // Argument of type 'null' is not assignable to parameter of type '{}'
func(undefined); // Argument of type 'undefined' is not assignable to parameter of type '{}'
const greeting = <T extends { name: string }>(obj: T) => {};
greeting({ name: "Foyez", age: 18 });
greeting({ age: 18 }); // error
interface User {
name: string;
age: number;
}
interface Admin {
name: string;
role: string;
}
function logPersion(person: User | Admin) {
if (person.age) {
// Property 'age' does not exist on type 'User | Admin'. Property 'age' does not exist on type 'Admin'
}
if ("role" in person) {
// work with Admin
}
}
type UppercaseWes = Uppercase<"wes">; // WES
type LowercaseWes = Lowercase<"Wes">; // wes
type CapitalizeWes = Capitalize<"wes">; // Wes
type UncapitalizeWes = Uncapitalize<"WEs">; // wEs