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.
export function add(x: number, y: number): number {
return x + y;
}
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.
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';
mixed = 42;
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];
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>;
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'];
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'>;
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'f'>;
type T2 = NonNullable<string | null | undefined>;
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>;
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') {
} else {
}
}
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) {
}
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);