10. TypeScript Fundamentals

10. TypeScript Fundamentals

Learning Objectives

  • Understand TypeScript advantages and relationship with JavaScript
  • Master the basic type system
  • Utilize interfaces and type aliases
  • Write reusable code with generics
  • Understand utility types and advanced type features

Table of Contents

  1. Introduction to TypeScript
  2. Basic Types
  3. Interfaces and Types
  4. Function Types
  5. Generics
  6. Utility Types
  7. Practice Problems

1. Introduction to TypeScript

1.1 What is TypeScript?

┌─────────────────────────────────────────────────────────────────┐
                    TypeScript Overview                           
                                                                 
   TypeScript = JavaScript + Static Types                       
                                                                 
   Features:                                                     
   - Developed by Microsoft                                      
   - Superset of JavaScript                                     
   - Compile-time type checking                                 
   - All JavaScript code is valid TypeScript                    
                                                                 
   Advantages:                                                   
   - Catch errors before runtime                                
   - Enhanced IDE support (autocomplete, refactoring)           
   - Code readability and documentation                         
   - Easy maintenance for large projects                        
└─────────────────────────────────────────────────────────────────┘

1.2 Installation and Setup

# Install TypeScript
npm install -g typescript

# Check version
tsc --version

# Initialize project
npm init -y
npm install typescript --save-dev

# Generate tsconfig.json
npx tsc --init
// tsconfig.json basic configuration
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "moduleResolution": "node"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

1.3 Compilation and Execution

# Compile single file
tsc hello.ts

# Compile entire project
tsc

# Watch mode (auto-compile on file changes)
tsc --watch

# ts-node for direct execution (development)
npm install -g ts-node
ts-node hello.ts

2. Basic Types

2.1 Primitive Types

// String
let name: string = "TypeScript";
let greeting: string = `Hello, ${name}!`;

// Number
let age: number = 25;
let price: number = 99.99;
let hex: number = 0xf00d;

// Boolean
let isActive: boolean = true;
let hasError: boolean = false;

// null and undefined
let nothing: null = null;
let notDefined: undefined = undefined;

// BigInt (ES2020+)
let bigNumber: bigint = 9007199254740991n;

// Symbol
let sym: symbol = Symbol("unique");

2.2 Arrays and Tuples

// Array types (two ways)
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: Array<string> = ["a", "b", "c"];

// Multi-dimensional arrays
let matrix: number[][] = [
  [1, 2, 3],
  [4, 5, 6],
];

// Tuple (fixed length, fixed type array)
let tuple: [string, number] = ["Alice", 30];
let rgb: [number, number, number] = [255, 128, 0];

// Tuple element access
const [userName, userAge] = tuple;
console.log(userName); // "Alice"

// Named tuples (improved readability)
type Point = [x: number, y: number];
const point: Point = [10, 20];

2.3 Object Types

// Basic object type
let person: { name: string; age: number } = {
  name: "Bob",
  age: 25,
};

// Optional properties (?)
let config: { host: string; port?: number } = {
  host: "localhost",
  // port is optional
};

// Readonly properties
let user: { readonly id: number; name: string } = {
  id: 1,
  name: "Alice",
};
// user.id = 2;  // Error! readonly

// Index signature
let dictionary: { [key: string]: number } = {
  apple: 1,
  banana: 2,
};

2.4 Special Types

// any - Allows all types (use sparingly)
let anything: any = "hello";
anything = 42;
anything = { foo: "bar" };

// unknown - Safer alternative to any
let unknownValue: unknown = "hello";
// unknownValue.toUpperCase();  // Error!
if (typeof unknownValue === "string") {
  unknownValue.toUpperCase(); // OK - after type guard
}

// void - No return value
function logMessage(msg: string): void {
  console.log(msg);
}

// never - Never returns
function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

2.5 Union and Intersection

// Union type (|) - One of several types
let id: string | number;
id = "abc";
id = 123;

type Status = "pending" | "approved" | "rejected";
let orderStatus: Status = "pending";

// Intersection type (&) - Combine all types
type Name = { name: string };
type Age = { age: number };
type Person = Name & Age;

const person: Person = {
  name: "Alice",
  age: 30,
};

2.6 Type Inference and Type Assertion

// Type inference - TypeScript automatically determines type
let message = "Hello"; // inferred as string
let count = 10; // inferred as number

// Type assertion
let someValue: unknown = "this is a string";

// Method 1: as syntax (recommended)
let strLength1: number = (someValue as string).length;

// Method 2: angle-bracket syntax (conflicts with JSX)
let strLength2: number = (<string>someValue).length;

// const assertion
let colors = ["red", "green", "blue"] as const;
// readonly ["red", "green", "blue"] type

// Non-null assertion (!)
function getLength(str: string | null): number {
  return str!.length; // Assert not null
}

3. Interfaces and Types

3.1 Interface Basics

// Interface definition
interface User {
  id: number;
  name: string;
  email: string;
  age?: number; // optional
  readonly createdAt: Date; // readonly
}

// Using interface
const user: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  createdAt: new Date(),
};

// Function type interface
interface Calculator {
  (a: number, b: number): number;
}

const add: Calculator = (a, b) => a + b;

3.2 Interface Extension

// Interface inheritance
interface Animal {
  name: string;
  age: number;
}

interface Dog extends Animal {
  breed: string;
  bark(): void;
}

const myDog: Dog = {
  name: "Buddy",
  age: 3,
  breed: "Labrador",
  bark() {
    console.log("Woof!");
  },
};

// Multiple inheritance
interface Pet extends Animal {
  owner: string;
}

interface ServiceDog extends Dog, Pet {
  certificationId: string;
}

3.3 Type Alias

// Type alias definition
type ID = string | number;
type Point = { x: number; y: number };
type Callback = (data: string) => void;

// Usage
let userId: ID = "user_123";
let position: Point = { x: 10, y: 20 };

// Useful for union types
type Result<T> = { success: true; data: T } | { success: false; error: string };

function fetchData(): Result<User> {
  return { success: true, data: { id: 1, name: "Alice", email: "a@b.com", createdAt: new Date() } };
}

3.4 Interface vs Type

// Interface - Declaration merging possible
interface Window {
  title: string;
}

interface Window {
  size: number; // Automatically merged
}

// Type - No merging, more flexible
type StringOrNumber = string | number; // Union
type Point = [number, number]; // Tuple

// Recommendations:
// - Object shape definition: use interface
// - Union, tuple, primitive type alias: use type
// - Library API: interface (extensible)

4. Function Types

4.1 Function Type Definition

// Function declaration
function add(a: number, b: number): number {
  return a + b;
}

// Arrow function
const multiply = (a: number, b: number): number => a * b;

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

const divide: MathOperation = (a, b) => a / b;

// Function type interface
interface MathFunc {
  (a: number, b: number): number;
  description?: string;
}

4.2 Parameter Options

// Optional parameters (?)
function greet(name: string, greeting?: string): string {
  return `${greeting || "Hello"}, ${name}!`;
}

// Default parameters
function greetWithDefault(name: string, greeting: string = "Hello"): string {
  return `${greeting}, ${name}!`;
}

// Rest parameters
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, n) => acc + n, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

4.3 Function Overloading

// Function overload signatures
function process(x: string): string;
function process(x: number): number;
function process(x: string | number): string | number {
  if (typeof x === "string") {
    return x.toUpperCase();
  }
  return x * 2;
}

console.log(process("hello")); // "HELLO"
console.log(process(5)); // 10

4.4 this Type

interface Button {
  label: string;
  click(this: Button): void;
}

const button: Button = {
  label: "Submit",
  click() {
    console.log(`Clicked: ${this.label}`);
  },
};

button.click(); // OK
// const handler = button.click;
// handler();  // Error! this context lost

5. Generics

5.1 Generic Basics

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

// Usage
let output1 = identity<string>("hello");
let output2 = identity<number>(42);
let output3 = identity("auto"); // Type inference

// Generic array
function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

const first = firstElement([1, 2, 3]); // number | undefined

5.2 Generic Interfaces and Types

// Generic interface
interface Box<T> {
  value: T;
}

const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };

// Generic type alias
type Result<T> = {
  success: boolean;
  data: T;
};

type Pair<K, V> = {
  key: K;
  value: V;
};

const pair: Pair<string, number> = { key: "age", value: 30 };

5.3 Generic Constraints

// Add constraints with extends
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength("hello"); // OK - string has length
logLength([1, 2, 3]); // OK - array has length
// logLength(123);    // Error! number has no length

// keyof constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const person = { name: "Alice", age: 30 };
const name = getProperty(person, "name"); // string
const age = getProperty(person, "age"); // number
// getProperty(person, "email");  // Error!

5.4 Generic Classes

class Queue<T> {
  private items: T[] = [];

  enqueue(item: T): void {
    this.items.push(item);
  }

  dequeue(): T | undefined {
    return this.items.shift();
  }

  peek(): T | undefined {
    return this.items[0];
  }

  get length(): number {
    return this.items.length;
  }
}

const numberQueue = new Queue<number>();
numberQueue.enqueue(1);
numberQueue.enqueue(2);
console.log(numberQueue.dequeue()); // 1

6. Utility Types

6.1 Basic Utility Types

interface User {
  id: number;
  name: string;
  email: string;
  age?: number;
}

// Partial<T> - Make all properties optional
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number }

// Required<T> - Make all properties required
type RequiredUser = Required<User>;
// { id: number; name: string; email: string; age: number }

// Readonly<T> - Make all properties readonly
type ReadonlyUser = Readonly<User>;

// Pick<T, K> - Select specific properties
type UserBasic = Pick<User, "id" | "name">;
// { id: number; name: string }

// Omit<T, K> - Exclude specific properties
type UserWithoutEmail = Omit<User, "email">;
// { id: number; name: string; age?: number }

6.2 Record and Mapping

// Record<K, T> - Key-value mapping
type UserRole = "admin" | "user" | "guest";
type RolePermissions = Record<UserRole, string[]>;

const permissions: RolePermissions = {
  admin: ["read", "write", "delete"],
  user: ["read", "write"],
  guest: ["read"],
};

// Usage example
type PageInfo = {
  title: string;
  url: string;
};

type Pages = Record<"home" | "about" | "contact", PageInfo>;

6.3 Conditional Types

// Exclude<T, U> - Exclude U from T
type Numbers = 1 | 2 | 3 | 4 | 5;
type SmallNumbers = Exclude<Numbers, 4 | 5>; // 1 | 2 | 3

// Extract<T, U> - Common types of T and U
type Common = Extract<"a" | "b" | "c", "a" | "c" | "d">; // "a" | "c"

// NonNullable<T> - Exclude null, undefined
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>; // string

// ReturnType<T> - Function return type
function getUser() {
  return { id: 1, name: "Alice" };
}
type UserReturn = ReturnType<typeof getUser>;
// { id: number; name: string }

// Parameters<T> - Function parameter types
type UserParams = Parameters<typeof getUser>; // []

6.4 Template Literal Types

// String literal combination
type Color = "red" | "green" | "blue";
type Size = "small" | "medium" | "large";

type ClassName = `${Size}-${Color}`;
// "small-red" | "small-green" | ... | "large-blue"

// Event name generation
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"

7. Practice Problems

Exercise 1: Type Definition

Define types for the following data structure.

// Example answer
interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
  inStock: boolean;
  tags?: string[];
}

interface CartItem {
  product: Product;
  quantity: number;
}

interface ShoppingCart {
  items: CartItem[];
  total: number;
  couponCode?: string;
}

Exercise 2: Generic Function

Write a generic function to find the first element in an array matching a condition.

// Example answer
function find<T>(arr: T[], predicate: (item: T) => boolean): T | undefined {
  for (const item of arr) {
    if (predicate(item)) {
      return item;
    }
  }
  return undefined;
}

// Usage
const numbers = [1, 2, 3, 4, 5];
const firstEven = find(numbers, (n) => n % 2 === 0); // 2

const users = [{ name: "Alice" }, { name: "Bob" }];
const alice = find(users, (u) => u.name === "Alice");

Exercise 3: Utility Type Usage

Define API response types.

// Example answer
interface ApiResponse<T> {
  success: boolean;
  data: T;
  error?: string;
  timestamp: number;
}

type User = {
  id: number;
  name: string;
  email: string;
};

type UserResponse = ApiResponse<User>;
type UsersResponse = ApiResponse<User[]>;
type DeleteResponse = ApiResponse<{ deleted: boolean }>;

// Update type using Partial
type UserUpdate = Partial<Omit<User, "id">>;

Next Steps

References

to navigate between lessons