Unit 5 - Notes
Unit 5: TypeScript Fundamentals
1. Introduction to TypeScript
What is TypeScript?
TypeScript is an open-source programming language developed and maintained by Microsoft. It is a strict syntactical superset of JavaScript, meaning any valid JavaScript code is also valid TypeScript code. TypeScript adds optional static typing to the language.
Key Characteristics
- Static Typing: Types are checked at compile time, not runtime. This catches errors early in the development cycle.
- Transpilation: Browsers and Node.js cannot execute TypeScript directly. It must be compiled (transpiled) into standard JavaScript.
- ECMAScript Features: TypeScript supports the latest ECMAScript standards (ES6, ES2020, etc.) and compiles them down to older versions (like ES5) for browser compatibility.
Benefits over JavaScript
- Early Error Detection: Catches typos, type mismatches, and logic errors before the code runs.
- Enhanced IDE Support: Provides robust autocompletion (IntelliSense), code navigation (Go to Definition), and refactoring tools.
- Scalability: Makes managing large codebases easier by making code self-documenting through types.
- Maintainability: easier to onboard new developers as the data structures are explicitly defined.
2. Type System and Annotations
The core of TypeScript is the ability to annotate variables, parameters, and return values with specific types.
Basic Primitive Types
- boolean:
trueorfalse. - number: Integers and floating-point values.
- string: Textual data.
let isDone: boolean = false;
let decimal: number = 6;
let color: string = "blue";
Special Types
- any: Opt-out of type checking. Allows any value. (Use sparingly).
- unknown: Safer version of
any. You cannot perform operations on anunknownvalue without narrowing the type first. - void: Represents the absence of a value (commonly used as the return type for functions that return nothing).
- null and undefined: Subtypes of all other types (unless
strictNullChecksis enabled). - never: Represents values that never occur (e.g., functions that throw errors or have infinite loops).
Arrays and Tuples
- Array: Lists of a single type.
- Tuple: Fixed-length arrays where each element has a known, specific type.
// Arrays
let list: number[] = [1, 2, 3];
let genericList: Array<number> = [1, 2, 3];
// Tuple
let x: [string, number];
x = ["hello", 10]; // OK
// x = [10, "hello"]; // Error
Function Annotations
You can annotate function parameters and return types.
// (parameter: type): returnType
function add(x: number, y: number): number {
return x + y;
}
// Optional parameters (?)
function greet(name: string, greeting?: string): void {
console.log(`${greeting || 'Hello'}, ${name}`);
}
3. Interfaces and Type Aliases
Both Interfaces and Type Aliases allow you to define the shape of an object or custom types.
Interfaces
Interfaces are primarily used to define the structure of objects. They support extending other interfaces.
interface User {
id: number;
username: string;
email?: string; // Optional property
readonly role: string; // Cannot be changed after creation
}
const user1: User = {
id: 1,
username: "dev_one",
role: "admin"
};
// Extending Interfaces
interface AdminUser extends User {
permissions: string[];
}
Type Aliases
Type aliases can define objects, but they can also define primitives, unions, and tuples.
type Point = {
x: number;
y: number;
};
// Type alias for a primitive
type ID = string;
// Type alias for a function signature
type Callback = (data: string) => void;
Interface vs. Type Alias
| Feature | Interface | Type Alias |
|---|---|---|
| Purpose | Defining object shapes | Defining shapes, unions, primitives |
| Extensibility | Can be merged (declaration merging) | Cannot be re-opened to add properties |
| Extension Syntax | extends |
Intersection & |
| Best Practice | Use for Objects/Classes APIs | Use for Unions, Primitives, Complex Functions |
4. Union and Intersection Types
These allow developers to combine existing types into new ones.
Union Types (|)
A variable with a union type can be one of several types. It represents an OR relationship.
let id: string | number;
id = 101; // Valid
id = "202-A"; // Valid
// id = true; // Error
// Literal Unions
type Direction = "North" | "South" | "East" | "West";
let move: Direction = "North";
Intersection Types (&)
An intersection type combines multiple types into one. The resulting object must have all properties of all intersected types. It represents an AND relationship.
interface Draggable {
drag: () => void;
}
interface Resizable {
resize: () => void;
}
type UIWidget = Draggable & Resizable;
let widget: UIWidget = {
drag: () => {},
resize: () => {}
};
5. Type Inference and Narrowing
TypeScript is smart enough to deduce types without explicit annotations, and it tracks how types change within control flow structures.
Type Inference
If you declare a variable and initialize it, TypeScript infers the type.
let message = "Hello World";
// TypeScript infers 'message' is type 'string'
// message = 100; // Error: Type 'number' is not assignable to type 'string'
Type Narrowing
When handling Union types, you must "narrow" the specific type before performing type-specific operations.
1. typeof Guard (Primitives)
function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase()); // TS knows id is string here
} else {
console.log(id.toFixed(2)); // TS knows id is number here
}
}
2. instanceof Guard (Classes)
class Dog { bark() {} }
class Cat { meow() {} }
function sound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
3. Discriminated Unions
Using a common literal property (the "discriminant") to distinguish between interfaces.
interface Success {
status: "success";
data: string;
}
interface Failure {
status: "error";
error: string;
}
type Response = Success | Failure;
function handleResponse(res: Response) {
if (res.status === "success") {
console.log(res.data); // TS knows this is Success
} else {
console.log(res.error); // TS knows this is Failure
}
}
6. Generics
Generics allow you to create reusable components that work with a variety of types rather than a single one, while still maintaining type safety. They are like variables for types.
Generic Functions
Using <T> (a conventional placeholder for "Type") to capture the type provided by the user.
// Without generics, we lose information (if using any)
// With generics:
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString"); // Type is string
let output2 = identity(100); // Type inferred as number
Generic Interfaces
interface Box<T> {
contents: T;
}
let stringBox: Box<string> = { contents: "Letters" };
let numberBox: Box<number> = { contents: 42 };
Generic Constraints
You can limit the types that can be passed to a generic by using extends.
interface Lengthwise {
length: number;
}
// T must have a .length property
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity({ length: 10, value: 3 }); // Valid
// loggingIdentity(3); // Error: Number doesn't have .length
7. TypeScript Compilation Workflow
Since TypeScript cannot run in the browser, understanding the compilation process is vital.
The Compiler (tsc)
The TypeScript Compiler converts .ts files into .js files.
- Command:
tsc filename.ts
tsconfig.json
This is the root configuration file for a TypeScript project. It specifies root files and compiler options.
- Initialization: Run
tsc --initto generate this file.
Key Configuration Options:
target: Specifies the ECMAScript target version (e.g.,"es5","es6","es2020"). Older targets maximize compatibility; newer targets produce cleaner code.module: Specifies the module code generation method (e.g.,"commonjs"for Node,"esnext"for modern frontend tools).outDir: The directory where compiled.jsfiles will be placed (e.g.,./dist).rootDir: The root directory of source.tsfiles (e.g.,./src).strict: Enables all strict type-checking options (strongly recommended).- Includes
noImplicitAny,strictNullChecks, etc.
- Includes
Workflow Example
- Write Code: Developer writes
src/index.ts. - Compile: Developer runs
tsc. - Transpile: TypeScript validates types.
- If errors: Compilation stops (or warns, depending on config), and errors are logged.
- If success:
dist/index.jsis generated.
- Run: The generated JavaScript is executed by the browser or Node.js.
Integration with Bundlers
In modern web development (React, Vue, Angular), tsc is often integrated into bundlers like Webpack, Vite, or Parcel. These tools handle the compilation on-the-fly during development (Hot Module Replacement) and bundling for production.