Introduction to TypeScript

TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. It adds static types to JavaScript, helping you catch errors early and making your code more robust and maintainable.

What is TypeScript?

TypeScript is a superset of JavaScript developed and maintained by Microsoft. It compiles to plain JavaScript and can be used in any JavaScript environment. TypeScript offers several key benefits:

  • Static typing: Catch type-related errors during development rather than at runtime
  • Enhanced IDE support: Better autocompletion, navigation, and refactoring tools
  • Improved readability: Type annotations serve as documentation
  • Advanced object-oriented features: Interfaces, generics, enums, and more
  • ECMAScript compatibility: Support for the latest JavaScript features

Note: TypeScript is a development tool. Your TypeScript code gets compiled to JavaScript before it runs in the browser or Node.js environment.

TypeScript vs JavaScript

Let's compare TypeScript and JavaScript with a simple example:

JavaScript TypeScript
function add(a, b) {
  return a + b;
}

// These all work, but some might not be what you intended
console.log(add(1, 2));       // 3
console.log(add("1", "2"));   // "12"
console.log(add(1, "2"));     // "12"
console.log(add(1, [2]));     // "12"
console.log(add(1, {}));      // "1[object Object]"
function add(a: number, b: number): number {
  return a + b;
}

// Only this works
console.log(add(1, 2));       // 3

// These all cause compile-time errors
// console.log(add("1", "2"));
// console.log(add(1, "2"));
// console.log(add(1, [2]));
// console.log(add(1, {}));

Getting Started with TypeScript

To use TypeScript, you need to install it and set up a project:

# Install TypeScript globally
npm install -g typescript

# Create a new directory and initialize a TypeScript project
mkdir ts-project
cd ts-project
npm init -y
tsc --init

The tsc --init command creates a tsconfig.json file that configures how TypeScript compiles your code.

Basic Types

TypeScript supports several basic types:

// Basic types
let isDone: boolean = false;
let decimal: number = 6;
let color: string = "blue";
let list: number[] = [1, 2, 3];
let tuple: [string, number] = ["hello", 10];
let u: undefined = undefined;
let n: null = null;

// Any type (avoid when possible)
let notSure: any = 4;
notSure = "maybe a string";
notSure = false;

// Void type (for functions that don't return a value)
function warnUser(): void {
  console.log("This is a warning message");
}

// Never type (for functions that never return)
function error(message: string): never {
  throw new Error(message);
}

// Object type
let obj: object = { key: "value" };

Interfaces

Interfaces define the structure that objects must adhere to:

interface Person {
  firstName: string;
  lastName: string;
  age?: number;  // Optional property
  readonly id: number;  // Can't be changed after creation
}

function greet(person: Person): string {
  return `Hello, ${person.firstName} ${person.lastName}`;
}

const john: Person = {
  firstName: "John",
  lastName: "Doe",
  id: 1
};

console.log(greet(john));  // "Hello, John Doe"

// Error: Cannot assign to 'id' because it is a read-only property
// john.id = 2;

Classes

TypeScript enhances JavaScript classes with type annotations and additional features:

class Animal {
  private name: string;
  protected species: string;
  
  constructor(name: string, species: string) {
    this.name = name;
    this.species = species;
  }
  
  public makeSound(): void {
    console.log("Some generic sound");
  }
  
  public getName(): string {
    return this.name;
  }
}

class Dog extends Animal {
  constructor(name: string) {
    super(name, "Canis familiaris");
  }
  
  public makeSound(): void {
    console.log("Woof! Woof!");
  }
  
  public getSpecies(): string {
    // Can access protected property from parent
    return this.species;
  }
}

const dog = new Dog("Rex");
console.log(dog.getName());  // "Rex"
dog.makeSound();  // "Woof! Woof!"
console.log(dog.getSpecies());  // "Canis familiaris"

// Error: Property 'name' is private and only accessible within class 'Animal'
// console.log(dog.name);

Generics

Generics allow you to create reusable components that work with a variety of types:

// Generic function
function identity(arg: T): T {
  return arg;
}

let output1 = identity("myString");  // type of output will be 'string'
let output2 = identity(100);         // type of output will be 'number'

// Generic interface
interface GenericIdentityFn {
  (arg: T): T;
}

let myIdentity: GenericIdentityFn = identity;

// Generic class
class Box {
  private value: T;
  
  constructor(value: T) {
    this.value = value;
  }
  
  getValue(): T {
    return this.value;
  }
}

const stringBox = new Box("Hello TypeScript");
const numberBox = new Box(42);

console.log(stringBox.getValue());  // "Hello TypeScript"
console.log(numberBox.getValue());  // 42

Type Assertions

Type assertions allow you to tell the compiler that you know better about the type of a value:

// Using angle-bracket syntax
let someValue: any = "this is a string";
let strLength: number = (someValue).length;

// Using as syntax (preferred in JSX)
let otherValue: any = "another string";
let otherLength: number = (otherValue as string).length;

Warning: Type assertions don't perform any type conversion. They're simply a way to tell the TypeScript compiler to treat a value as a specific type.

Union and Intersection Types

TypeScript allows you to combine types in various ways:

// Union types (value can be one of several types)
function formatValue(value: string | number): string {
  if (typeof value === "string") {
    return value.toUpperCase();
  }
  return value.toFixed(2);
}

console.log(formatValue("hello"));  // "HELLO"
console.log(formatValue(42.123));   // "42.12"

// Intersection types (value has all properties of each type)
interface Named {
  name: string;
}

interface Aged {
  age: number;
}

type Person = Named & Aged;

function greet(person: Person): string {
  return `Hello, ${person.name}! You are ${person.age} years old.`;
}

const john: Person = {
  name: "John",
  age: 30
};

console.log(greet(john));  // "Hello, John! You are 30 years old."

Enums

Enums allow you to define a set of named constants:

// Numeric enum
enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right    // 3
}

// String enum
enum Color {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE"
}

// Usage
function move(direction: Direction): void {
  switch (direction) {
    case Direction.Up:
      console.log("Moving up");
      break;
    case Direction.Down:
      console.log("Moving down");
      break;
    case Direction.Left:
      console.log("Moving left");
      break;
    case Direction.Right:
      console.log("Moving right");
      break;
  }
}

move(Direction.Up);  // "Moving up"

// Reverse mapping (only works with numeric enums)
console.log(Direction[2]);  // "Left"

Type Aliases

Type aliases create a new name for a type:

// Simple type alias
type Point = {
  x: number;
  y: number;
};

// Function type alias
type OperationFn = (a: number, b: number) => number;

// Union type alias
type Result = { success: true, value: T } | { success: false, error: string };

// Usage
const origin: Point = { x: 0, y: 0 };

const add: OperationFn = (a, b) => a + b;
const subtract: OperationFn = (a, b) => a - b;

function divide(a: number, b: number): Result {
  if (b === 0) {
    return { success: false, error: "Division by zero" };
  }
  return { success: true, value: a / b };
}

const result = divide(10, 2);
if (result.success) {
  console.log(`Result: ${result.value}`);
} else {
  console.log(`Error: ${result.error}`);
}

TypeScript with Modern JavaScript

TypeScript supports all modern JavaScript features and adds type annotations:

// Destructuring with type annotations
function printPerson({ name, age }: { name: string; age: number }): void {
  console.log(`${name} is ${age} years old`);
}

// Optional chaining and nullish coalescing
type User = {
  name: string;
  address?: {
    street?: string;
    city?: string;
  };
};

function getCity(user: User): string {
  return user.address?.city ?? "Unknown";
}

// Template literal types
type EventName = `on${string}`;
type Direction = "top" | "right" | "bottom" | "left";
type Position = `${Direction}-${number}`;

const validEvent: EventName = "onClick";
const position: Position = "top-10";

Compiling TypeScript

To compile TypeScript code to JavaScript, use the TypeScript compiler (tsc):

# Compile a single file
tsc app.ts

# Compile all files in the project according to tsconfig.json
tsc

# Compile and watch for changes
tsc --watch

TypeScript Configuration

The tsconfig.json file controls TypeScript compilation options:

{
  "compilerOptions": {
    "target": "es2020",           // ECMAScript target version
    "module": "commonjs",         // Module system
    "strict": true,               // Enable all strict type-checking options
    "esModuleInterop": true,      // Enables compatibility with Babel
    "skipLibCheck": true,         // Skip type checking of declaration files
    "forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references
    "outDir": "./dist",           // Output directory for compiled files
    "rootDir": "./src"            // Root directory of source files
  },
  "include": ["src/**/*"],        // Files to include
  "exclude": ["node_modules"]     // Files to exclude
}

Interactive Example

Try out this interactive example to see TypeScript type checking in action:

TypeScript in Modern Frameworks

TypeScript is widely used in modern JavaScript frameworks and libraries:

  • Angular: Built with TypeScript and uses it extensively
  • React: Supports TypeScript through @types/react and create-react-app --template typescript
  • Vue.js: Supports TypeScript through class-based components or the Composition API
  • Node.js: Works well with TypeScript using ts-node or compilation
  • Express: Can be used with TypeScript via @types/express

Best Practices

  • Be explicit with types: Avoid using any when possible
  • Use interfaces for object shapes: They're more flexible and can be extended
  • Enable strict mode: Set "strict": true in your tsconfig.json
  • Use type inference: Let TypeScript infer types when it's obvious
  • Document with JSDoc: Add JSDoc comments for better IDE tooltips
  • Use linting: Configure ESLint with TypeScript support

Next Steps

Now that you understand the basics of TypeScript, you might want to explore:

  • Advanced TypeScript features like conditional types and mapped types
  • TypeScript with React or Angular
  • Type-safe API calls with TypeScript
  • Testing TypeScript applications
  • TypeScript design patterns