Understanding Primitive and Complex Types
TypeScript extends JavaScript by adding a robust type system, enabling developers to define the shapes and types of values used in their applications. This strong typing helps catch errors early in development and improves code clarity.
Primitive Types
TypeScript supports all JavaScript primitive types, including:
number: For both integers and floating-point numbers.string: For textual data.boolean: For true/false values.nullandundefined: Representing absence of value.symbol: For unique, immutable values (ES6 feature).bigint: For arbitrarily large integers (ES2020 feature).
let productName: string = "TypeScript Handbook";
let itemCount: number = 150;
let isActive: boolean = true;
let uniqueId: symbol = Symbol("unique_identifier");
Array Types
Arrays can be typed in TypeScript using two common syntaxes:
- Using
Type[]: - Using the generic
Array<Type>:
// An array of numbers
let numericValues: number[] = [10, 20, 30];
// An array holding mixed types
let mixedCollection: Array<number | string> = [5, "alpha", 10, "beta"];
Tuple Types
Tuples allow you to express an array where the type of a fixed number of elements is known, but they don't have to be the same. This is particularly useful for representing records or data pairs.
// A tuple representing a user profile: [ID (number), Username (string), IsActive (boolean)]
let userProfile: [number, string, boolean] = [101, "adminUser", true];
console.log(userProfile[0]); // Output: 101
console.log(userProfile[1].toUpperCase()); // Output: ADMINUSER
Object Type
The object type represents any non-primitive value, i.e., anything that isn't a number, string, boolean, symbol, null, or undefined.
let configuration: object = { host: "localhost", port: 8080 };
// Note: You generally want to define a more specific shape for objects using interfaces or type aliases.
Leveraging Enums for Constant Values
Enums (enumerations) provide a way to define a set of named constants. They improve code readability and maintainability by relpacing "magic numbers" or strings with meaningful names.
Numeric Enums
By default, enums are number-based and start indexing from 0, or from a specified value.
enum PermissionLevel {
Viewer, // 0
Editor = 5, // 5
Administrator, // 6 (automatically increments from Editor)
Owner // 7
}
console.log(PermissionLevel.Editor); // Output: 5
console.log(PermissionLevel[6]); // Output: Administrator (reverse mapping)
String Enums
String enums are similar to numeric enums, but their members are initialized with string literal values. String enums do not have auto-incrementing behavior.
enum EventStatus {
Pending = "PENDING",
InProgress = "IN_PROGRESS",
Completed = "COMPLETED",
Failed = "FAILED"
}
console.log(EventStatus.Completed); // Output: COMPLETED
const enum for Performance
Using const enum prevents the generation of extra code at compile time. Instead, enum members are inlined where they are used, leading to smaller JavaScript bundles, especially when enum members are used directly in conditional checks.
const enum HttpMethod {
Get = "GET",
Post = "POST",
Put = "PUT",
Delete = "DELETE"
}
function handleHttpRequest(method: HttpMethod) {
if (method === HttpMethod.Get) {
// Process GET request
console.log("Handling GET request...");
} else if (method === HttpMethod.Post) {
// Process POST request
console.log("Handling POST request...");
}
}
handleHttpRequest(HttpMethod.Get);
// In the compiled JavaScript, 'HttpMethod.Get' would be replaced directly with the string "GET".
Defining Custom Types with Interfaces
Interfaces in TypeScript are powerful tools for defining the shape of objects. They enforce that an object conforms to a specific structure, making your code more predictable.
Interface for Array-like Structures (Index Signatures)
Interfaces can also describe dictionary-like types or arrays where elements are accessed by an index.
interface NamedCollection {
[index: number]: string; // Defines that any numeric index will return a string
// You can also add other properties, e.g., 'name: string;'
}
let productNames: NamedCollection = ['Laptop', 'Mouse', 'Keyboard'];
console.log(productNames[0]); // Output: Laptop
Function Type Interfaces and Type Aliases
TypeScript allows you to define the type signature of functions, ensuring that functions adhere to specific parameter and return types. This can be done inline, using interfaces, or using type aliases.
Enline Functon Type
let calculateSum: (x: number, y: number) => number;
calculateSum = (a, b) => a + b;
console.log(calculateSum(10, 20)); // Output: 30
Function Type Interface
interface MathOperation {
(operand1: number, operand2: number): number;
}
let multiply: MathOperation;
multiply = (x, y) => x * y;
console.log(multiply(5, 4)); // Output: 20
Function Type Alias
type BinaryOperation = (arg1: number, arg2: number) => number;
let subtract: BinaryOperation = (a, b) => a - b;
console.log(subtract(30, 15)); // Output: 15
Function Overloading
Function overloading enables you to define multiple function signatures for a single function implementation. TypeScript chooses the correct overload based on the types and number of arguments provided at the call site.
function formatValue(input: string): string;
function formatValue(input: number): number;
function formatValue(input: string | number): string | number {
if (typeof input === 'string') {
return input.toUpperCase(); // Convert string to uppercase
} else {
return input * 2; // Double the number
}
}
console.log(formatValue("hello")); // Output: HELLO
console.log(formatValue(10)); // Output: 20
// console.log(formatValue(true)); // Error: No overload matches this call.
Object-Oriented Programming with Classes
TypeScript provides full support for ES6 classes, including constructors, properties, methods, and inheritance, along with access modifiers.
Basic Class Definition and Instantiation
Classes serve as blueprints for creating objects. They encapsulate data (properties) and behavior (methods).
class Vehicle {
public model: string; // Public by default, can be explicitly declared
private year: number; // Accessible only within the Vehicle class
constructor(modelName: string, manufacturingYear: number) {
this.model = modelName;
this.year = manufacturingYear;
}
drive(): string {
return `${this.model} from ${this.year} is now driving.`;
}
}
const myCar = new Vehicle("Sedan", 2023);
console.log(myCar.model); // Output: Sedan
console.log(myCar.drive()); // Output: Sedan from 2023 is now driving.
// console.log(myCar.year); // Error: Property 'year' is private.
Class Inheritance
Inheritance allows a class (subclass) to inherit properties and methods from another class (superclass), promoting code reuse.
class ElectricVehicle extends Vehicle {
public batteryCapacityKWH: number;
constructor(modelName: string, manufacturingYear: number, capacity: number) {
super(modelName, manufacturingYear); // Call the parent class's constructor
this.batteryCapacityKWH = capacity;
}
charge(): string {
return `${this.model} is charging its ${this.batteryCapacityKWH} kWh battery.`;
}
}
const tesla = new ElectricVehicle("Model 3", 2024, 75);
console.log(tesla.drive()); // Inherited method
console.log(tesla.charge()); // New method
Classes and Interfaces: Defining Contracts
Interfaces can be used to define a contract that classes must adhere to. A class implementing an interface must provide an implementation for all members declared in the interface.
interface Loggable {
log(message: string): void;
timestamp: Date;
}
class SystemLogger implements Loggable {
timestamp: Date = new Date();
log(message: string): void {
console.log(`[${this.timestamp.toISOString()}] ${message}`);
}
}
const logger = new SystemLogger();
logger.log("Application started successfully.");