Skip to main content

Typescript Notes

· 8 min read

Basics

Types: The core of TypeScript. You define the type of your variables.

let name: string = 'Alex';
let age: number = 25;

Interfaces: These let you define the shape of objects.

interface Person {
name: string;
age: number;
}

let user: Person = { name: 'Tom Yum', age: 25 };

Functions: You can specify types for parameters and return values.

function greet(name: string): string {
return 'Hello, ' + name;
}

Classes: TypeScript supports modern JavaScript classes with a bit extra.

class Animal {
constructor(public name: string) {}
move(distance: number): void {
console.log(`${this.name} moved ${distance}m.`);
}
}

Generics: These provide a way to create reusable components.

function identity<T>(arg: T): T {
return arg;
}

Namespace & Modules

Modules: Recommended over namespaces for most use cases. Great for structuring large-scale applications and managing dependencies.

// In file math.ts
export function add(x: number, y: number): number {
return x + y;
}

// In another file
import { add } from './math';
console.log(add(5, 3));

Namespaces: An older TypeScript feature for organizing code. Good for internal organization of large codebases.

namespace MyApplication {
export class MyClass {
/* ... */
}
}

Dynamic Import Expressions: TypeScript supports dynamic imports, allowing you to import modules dynamically.

async function loadModule() {
const myModule = await import('./MyModule');
const myModuleInstance = new myModule.MyClass();
}

Module Augmentation: Modify existing module declarations to add new functionality or modify existing one.

// Extending an existing module
declare module 'module-name' {
export interface ModuleInterface {
newFunction(): void;
}
}

Advanced Features

Union Types: Combine multiple types into one. Super useful for variables that can hold more than one type.

let mixed: number | string;
mixed = 'hello'; // OK
mixed = 42; // Also OK

Type Aliases: Create a new name for a type. It's like making a shortcut.

type StringOrNumber = string | number;
let value: StringOrNumber;

Enums: A way to define a set of named constants. Makes your code more readable.

enum Color {
Red,
Green,
Blue,
}
let c: Color = Color.Green;

Tuples: Arrays where the type of a fixed number of elements is known, but doesn't have to be the same.

let x: [string, number];
x = ['hello', 10]; // OK

Optional Chaining and Nullish Coalescing: New JavaScript features that TypeScript supports for safer code.

#Optional Chaining: Safely access deeply nested properties.

const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
const catName = adventurer.cat?.name;

#Nullish Coalescing: Deal with null or undefined values elegantly.

const input = '';
const storedData = input ?? 'Default';

Advanced Types

Intersection Types: Combine multiple types into one. It's like extending interfaces, but more flexible.

type Employee = {
name: string;
startDate: Date;
};

type Manager = Employee & {
reports: Employee[];
};

Mapped Types: Create types based on existing types. They're like a dynamic way to generate types.

type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

type ReadonlyEmployee = Readonly<Employee>;

Conditional Types: Types that change based on conditions.

type Check<T> = T extends string ? 'String' : 'Other';
type TypeCheck = Check<string>; // 'String'

Utility Types: TypeScript provides several utility types to modify existing types, like Partial, Readonly, and Record.

type PartialEmployee = Partial<Employee>;
type ReadonlyEmployee = Readonly<Employee>;

Indexed Access Types: Allow you to access the type of a property of another type.

type Person = { name: string; age: number };
type PersonName = Person['name']; // string

Index Signatures: Useful when you don't know all the property names of an object.

interface StringMap {
[key: string]: string;
}
const obj: StringMap = { name: 'Alice', job: 'Developer' };

Type Guards: Helps in narrowing down the type of an object within a conditional block.

function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}

Advanced Types Utility Functions

Partial, Readonly, Record, and Pick: These built-in utility types offer shortcuts for common type transformations.

type PartialPerson = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;
type PersonRecord = Record<string, Person>;
type PickedPerson = Pick<Person, 'name' | 'age'>;

Exclude, Extract, NonNullable: These utilities help in manipulating union types and nullable types.

type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // "b" | "c"
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'f'>; // "a"
type T2 = NonNullable<string | null | undefined>; // string

Omit Type: Removes keys from a type, creating a new type.

type OmitAge = Omit<Person, 'age'>;

ReturnType: Constructs a type consisting of the return type of a function.

type ExampleFunction = () => number;
type FunctionReturnType = ReturnType<ExampleFunction>; // number

Advanced Type Manipulation

Type Assertions: Similar to type casting in other languages, allows you to override the inferred type.

let someValue: unknown = 'this is a string';
let strLength: number = (someValue as string).length;

Discriminated Unions: Useful in scenarios where you have types that could be several things.

interface Circle {
kind: 'circle';
radius: number;
}

interface Square {
kind: 'square';
sideLength: number;
}

type Shape = Circle | Square;

function handleShape(shape: Shape) {
if (shape.kind === 'circle') {
// handle circle
} else {
// handle square
}
}

Type Predicates: Used to narrow types using functions.

function isCircle(shape: Shape): shape is Circle {
return shape.kind === 'circle';
}

Advanced Function Types

Overloads: TypeScript allows function overloading for more flexible code.

function greet(name: string): string;
function greet(age: number): string;
function greet(value: string | number): string {
if (typeof value === 'string') {
return `Hello, ${value}`;
} else {
return `Age: ${value}`;
}
}

Rest Parameters and Spread Operator: Handy for functions with a variable number of arguments.

function multiply(n: number, ...m: number[]): number[] {
return m.map((x) => n * x);
}

Decorators

Special syntax for adding annotations and a meta-programming syntax for class declarations and members.

Class Decorators: Applied to the constructor of a class and can be used to observe, modify, or replace a class definition.

function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}

@sealed
class BugReport {
type = 'report';
title: string;

constructor(t: string) {
this.title = t;
}
}

Method Decorators: Applied to method declarations and can be used to observe, modify, or replace method definitions.

function enumerable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value;
};
}

class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}

@enumerable(false)
greet() {
return 'Hello, ' + this.greeting;
}
}

Parameter Decorators: Used to annotate or modify the behavior of method parameters.

function logParameter(target: Object, propertyName: string, index: number) {
// logic to log or modify parameter
}

class Greeter {
greeting(name: string, @logParameter message: string): string {
return `${name} says: ${message}`;
}
}

Accessor Decorators: Applied to getters/setters.

function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}

class Point {
private _x: number;
private _y: number;

constructor(x: number, y: number) {
this._x = x;
this._y = y;
}

@configurable(false)
get x() {
return this._x;
}

@configurable(false)
get y() {
return this._y;
}
}

TypeScript with Async/Await

TypeScript supports modern JavaScript features like async/await, making working with asynchronous code much easier.

Async Functions: An async function is a function declared with the async keyword. It can contain await expressions.

async function fetchData(): Promise<Data> {
const response = await fetch('https://api.example.com/data');
const data: Data = await response.json();
return data;
}

Error Handling: Use try-catch blocks for error handling in async functions.

async function safeFetchData(url: string): Promise<Data | null> {
try {
const response = await fetch(url);
const data: Data = await response.json();
return data;
} catch (error) {
console.error('Fetch error:', error);
return null;
}
}

Promise.all: When you have multiple asynchronous operations that can run concurrently, use Promise.all to wait for all of them to finish.

async function fetchMultipleUrls(urls: string[]): Promise<Data[]> {
const promises = urls.map((url) =>
fetch(url).then((response) => response.json())
);
return Promise.all(promises);
}

Promise.allSettled: Similar to Promise.all, but waits for all promises to settle, regardless of whether they are fulfilled or rejected.

async function fetchWithAllSettled(urls: string[]): Promise<(Data | Error)[]> {
const promises = urls.map((url) =>
fetch(url)
.then((response) => response.json())
.catch((error) => error)
);
return Promise.allSettled(promises);
}

Async Iterators and Generators: which are great for handling streams of data asynchronously.

async function* asyncGenerator() {
let i = 0;
while (i < 3) {
yield await new Promise(resolve => setTimeout(() => resolve(i++), 1000));
}
}

async function runAsyncGenerator() {
for await (let value of asyncGenerator()) {
console.log(value);
}
}

Working with JSON in TypeScript

When dealing with JSON data, type assertion can be used to ensure the JSON structure matches your expected type.

const data = JSON.parse(jsonString) as MyInterface;

Mixins

Mixins are a way to add functionality to classes. TypeScript supports mixins through its more abstract type system.

type Constructor<T = {}> = new (...args: any[]) => T;

function TimesStamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
timestamp = Date.now();
};
}

class User {
name = '';
}

const TimesStampedUser = TimesStamped(User);

const user = new TimesStampedUser();
console.log(user.timestamp); // Logs the current timestamp