Prerequisites
- Basic TypeScript function knowledge ๐
- Understanding of arrays and objects โก
- TypeScript development environment ๐ป
What you'll learn
- Master rest parameters for variable arguments ๐ฏ
- Use spread operator with arrays and objects ๐๏ธ
- Combine rest and spread effectively ๐
- Apply patterns in real-world scenarios โจ
๐ฏ Introduction
Welcome to the dynamic world of rest parameters and spread operator in TypeScript! ๐ In this guide, weโll explore how to handle variable-length arguments and manipulate arrays and objects with elegance.
Imagine youโre organizing a party ๐ - you donโt know exactly how many guests will come, but you want to welcome them all! Thatโs what rest parameters do for your functions. And the spread operator? Itโs like unpacking a suitcase ๐ฆผ - taking everything inside and spreading it out!
By the end of this tutorial, youโll be spreading and gathering data like a TypeScript ninja! Letโs dive in! ๐โโ๏ธ
๐ Understanding Rest Parameters and Spread Operator
๐ค What Are Rest Parameters?
Rest parameters are like a magic basket ๐งบ that collects all remaining arguments into an array. Theyโre represented by three dots ...
before a parameter name:
function collectAll(...items: string[]) {
// items is an array containing all arguments!
}
Think of it as saying โgive me the rest of the arguments!โ ๐
๐ก What Is the Spread Operator?
The spread operator looks the same (...
) but works in reverse - it unpacks elements from arrays or properties from objects:
const numbers = [1, 2, 3];
console.log(...numbers); // 1 2 3 - spreads the array!
Itโs like opening a box ๐ฆ and taking everything out!
๐ฏ Why Use Them?
Hereโs why these features are game-changers:
- Flexible Functions ๐: Accept any number of arguments
- Clean Array/Object Operations ๐งฝ: Copy, merge, and manipulate with ease
- Type Safety ๐ก๏ธ: TypeScript tracks types through operations
- Readable Code ๐: Express intent clearly
- Modern Patterns ๐: Essential for contemporary JavaScript/TypeScript
๐ง Basic Syntax and Usage
๐ Rest Parameters Basics
Letโs start with rest parameters:
// ๐จ Basic rest parameter
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6 โ
console.log(sum(1, 2, 3, 4, 5)); // 15 โ
console.log(sum()); // 0 โ
// ๐ Rest parameters with other parameters
function greetAll(greeting: string, ...names: string[]): void {
names.forEach(name => {
console.log(`${greeting}, ${name}! ๐`);
});
}
greetAll("Hello", "Alice", "Bob", "Charlie");
// Output:
// Hello, Alice! ๐
// Hello, Bob! ๐
// Hello, Charlie! ๐
// ๐ฏ Type-safe rest parameters
function logMessages(level: "info" | "warn" | "error", ...messages: string[]): void {
const emoji = {
info: "โน๏ธ",
warn: "โ ๏ธ",
error: "โ"
};
console.log(`${emoji[level]} [${level.toUpperCase()}]`, ...messages);
}
logMessages("info", "Server started", "Port 3000");
logMessages("error", "Connection failed", "Retry in 5s");
๐จ Spread Operator with Arrays
Now letโs explore the spread operator:
// ๐ Array spreading basics
const fruits = ["๐", "๐", "๐"];
const vegetables = ["๐ฅ", "๐ฅฆ", "๐"];
// Combine arrays
const food = [...fruits, ...vegetables];
console.log(food); // ["๐", "๐", "๐", "๐ฅ", "๐ฅฆ", "๐"]
// ๐จ Copy arrays (no mutation!)
const originalScores = [100, 85, 92];
const copiedScores = [...originalScores];
copiedScores.push(88);
console.log(originalScores); // [100, 85, 92] โ
Original unchanged!
console.log(copiedScores); // [100, 85, 92, 88]
// ๐ฏ Insert elements
const numbers = [1, 2, 5, 6];
const complete = [...numbers.slice(0, 2), 3, 4, ...numbers.slice(2)];
console.log(complete); // [1, 2, 3, 4, 5, 6]
// ๐๏ธ Convert iterables to arrays
function lettersToArray(word: string): string[] {
return [...word]; // Spreads string into character array!
}
console.log(lettersToArray("Hello")); // ["H", "e", "l", "l", "o"]
๐ Spread Operator with Objects
// ๐ Object spreading basics
interface User {
name: string;
age: number;
email: string;
}
const baseUser: User = {
name: "Alice",
age: 30,
email: "[email protected]"
};
// ๐จ Create a copy with updates
const updatedUser = {
...baseUser,
age: 31,
emoji: "๐"
};
console.log(baseUser); // Original unchanged
console.log(updatedUser); // New object with updates
// ๐ฏ Merge objects
const defaults = {
theme: "light",
language: "en",
notifications: true
};
const userPrefs = {
theme: "dark",
fontSize: 16
};
const finalSettings = {
...defaults,
...userPrefs // Later spreads override earlier ones
};
// Result: { theme: "dark", language: "en", notifications: true, fontSize: 16 }
// ๐ก๏ธ Type-safe object composition
interface Animal {
name: string;
species: string;
}
interface Pet extends Animal {
owner: string;
trained: boolean;
}
const animal: Animal = { name: "Max", species: "dog" };
const pet: Pet = {
...animal,
owner: "Sarah",
trained: true
};
๐ก Practical Examples
๐ Example 1: Flexible Shopping Cart
Letโs build a shopping cart that accepts multiple items at once:
// ๐๏ธ Product interface
interface Product {
id: string;
name: string;
price: number;
category: string;
emoji: string;
}
class ShoppingCart {
private items: Product[] = [];
// ๐ฏ Add multiple items at once using rest parameters
addItems(...products: Product[]): void {
this.items.push(...products); // Spread to add all!
console.log(`๐ Added ${products.length} items to cart!`);
products.forEach(p => console.log(` ${p.emoji} ${p.name}`));
}
// ๐ฐ Calculate total with optional tax rates
calculateTotal(...taxRates: number[]): number {
const subtotal = this.items.reduce((sum, item) => sum + item.price, 0);
const totalTaxRate = taxRates.reduce((sum, rate) => sum + rate, 0) / 100;
const tax = subtotal * totalTaxRate;
console.log(`๐ Subtotal: $${subtotal.toFixed(2)}`);
if (taxRates.length > 0) {
console.log(`๐ Tax (${totalTaxRate * 100}%): $${tax.toFixed(2)}`);
}
console.log(`๐ณ Total: $${(subtotal + tax).toFixed(2)}`);
return subtotal + tax;
}
// ๐ท๏ธ Filter by categories
filterByCategories(...categories: string[]): Product[] {
return this.items.filter(item =>
categories.includes(item.category)
);
}
// ๐ Clone cart with modifications
cloneAndModify(modifications: Partial<Product>): ShoppingCart {
const newCart = new ShoppingCart();
const modifiedItems = this.items.map(item => ({
...item,
...modifications
}));
newCart.addItems(...modifiedItems);
return newCart;
}
}
// ๐ฎ Usage
const cart = new ShoppingCart();
// Add multiple items at once
cart.addItems(
{ id: "1", name: "TypeScript Handbook", price: 39.99, category: "books", emoji: "๐" },
{ id: "2", name: "Mechanical Keyboard", price: 149.99, category: "electronics", emoji: "โจ๏ธ" },
{ id: "3", name: "Coffee Beans", price: 15.99, category: "food", emoji: "โ" }
);
// Calculate with multiple tax rates
cart.calculateTotal(8.5, 2); // State tax + local tax
// Filter electronics
const electronics = cart.filterByCategories("electronics", "computers");
console.log(`\n๐ฑ Electronics:`, electronics.map(e => e.emoji));
๐จ Example 2: Event System with Rest & Spread
Letโs create a flexible event system:
// ๐ Event system with variable arguments
type EventCallback = (...args: any[]) => void;
class EventEmitter {
private events: Map<string, EventCallback[]> = new Map();
// ๐ข Subscribe to events
on(event: string, ...callbacks: EventCallback[]): void {
if (!this.events.has(event)) {
this.events.set(event, []);
}
const handlers = this.events.get(event)!;
handlers.push(...callbacks); // Spread to add all callbacks
console.log(`๐ Subscribed ${callbacks.length} handlers to "${event}"`);
}
// ๐ฅ Emit events with any number of arguments
emit(event: string, ...args: any[]): void {
const handlers = this.events.get(event) || [];
console.log(`๐ก Emitting "${event}" to ${handlers.length} handlers`);
handlers.forEach(handler => {
handler(...args); // Spread arguments to handler
});
}
// ๐บ๏ธ Create a filtered emitter
createFilteredEmitter(...allowedEvents: string[]): EventEmitter {
const filtered = new EventEmitter();
// Copy only allowed events
allowedEvents.forEach(event => {
const handlers = this.events.get(event);
if (handlers) {
filtered.on(event, ...handlers);
}
});
return filtered;
}
}
// ๐ฎ Usage
const gameEvents = new EventEmitter();
// Subscribe multiple handlers at once
gameEvents.on("player-join",
(name: string) => console.log(`๐ฎ ${name} joined the game!`),
(name: string) => console.log(`๐ Welcome ${name}!`),
(name: string) => console.log(`๐ฅ Players online: updating...`)
);
gameEvents.on("score-update",
(player: string, points: number, bonus?: string) => {
console.log(`๐ ${player} scored ${points} points!`);
if (bonus) console.log(` ๐ Bonus: ${bonus}`);
}
);
// Emit with variable arguments
gameEvents.emit("player-join", "Alice");
gameEvents.emit("score-update", "Alice", 100, "First Kill!");
๐ฎ Example 3: Configuration Merger
Letโs build a powerful configuration system:
// ๐ง Deep configuration merger
interface Config {
[key: string]: any;
}
class ConfigManager {
// ๐จ Merge multiple configs with precedence
static merge(...configs: Config[]): Config {
return configs.reduce((merged, config) => {
return this.deepMerge(merged, config);
}, {});
}
// ๐๏ธ Deep merge helper
private static deepMerge(target: Config, source: Config): Config {
const result = { ...target }; // Spread to copy
Object.keys(source).forEach(key => {
if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = this.deepMerge(result[key] || {}, source[key]);
} else if (Array.isArray(source[key])) {
result[key] = [...(result[key] || []), ...source[key]]; // Merge arrays
} else {
result[key] = source[key];
}
});
return result;
}
// ๐ Create environment-specific config
static createEnvConfig(base: Config, ...environments: Array<[string, Config]>): Map<string, Config> {
const envConfigs = new Map<string, Config>();
environments.forEach(([env, config]) => {
envConfigs.set(env, this.merge(base, config));
console.log(`๐ฏ Created config for ${env} environment`);
});
return envConfigs;
}
}
// ๐ Usage
const defaultConfig = {
app: {
name: "MyApp",
version: "1.0.0",
features: ["basic"]
},
server: {
port: 3000,
host: "localhost"
},
database: {
type: "postgres",
pool: { min: 2, max: 10 }
}
};
const productionConfig = {
server: {
port: 443,
host: "api.myapp.com",
ssl: true
},
database: {
pool: { min: 10, max: 50 },
ssl: { rejectUnauthorized: true }
},
app: {
features: ["premium", "analytics"]
}
};
const developmentConfig = {
server: {
debug: true
},
app: {
features: ["debug-panel"]
}
};
// Merge configs
const finalConfig = ConfigManager.merge(
defaultConfig,
productionConfig,
{ app: { emoji: "๐" } }
);
console.log("๐จ Final config:", finalConfig);
// Create environment configs
const envConfigs = ConfigManager.createEnvConfig(
defaultConfig,
["production", productionConfig],
["development", developmentConfig]
);
๐ Advanced Concepts
๐งโโ๏ธ Generic Rest Parameters
Level up with type-safe generic rest parameters:
// ๐ฏ Type-safe pipe function
function pipe<T extends any[], R>(
fn1: (...args: T) => R
): (...args: T) => R;
function pipe<T extends any[], R1, R2>(
fn1: (...args: T) => R1,
fn2: (arg: R1) => R2
): (...args: T) => R2;
function pipe<T extends any[], R1, R2, R3>(
fn1: (...args: T) => R1,
fn2: (arg: R1) => R2,
fn3: (arg: R2) => R3
): (...args: T) => R3;
function pipe(...fns: Function[]) {
return (...args: any[]) => {
return fns.reduce((result, fn, index) => {
return index === 0 ? fn(...args) : fn(result);
}, undefined);
};
}
// ๐ Usage
const addTax = (price: number) => price * 1.2;
const format = (price: number) => `$${price.toFixed(2)}`;
const addEmoji = (price: string) => `๐ฐ ${price}`;
const priceProcessor = pipe(addTax, format, addEmoji);
console.log(priceProcessor(100)); // "๐ฐ $120.00"
// ๐จ Type-safe tuple spread
type Head<T extends readonly unknown[]> = T extends readonly [infer H, ...unknown[]] ? H : never;
type Tail<T extends readonly unknown[]> = T extends readonly [unknown, ...infer Rest] ? Rest : [];
function first<T extends readonly unknown[]>(...args: T): Head<T> {
return args[0] as Head<T>;
}
function rest<T extends readonly unknown[]>(...args: T): Tail<T> {
const [, ...tail] = args;
return tail as Tail<T>;
}
// Usage
const head = first(1, "hello", true); // type: number
const tail = rest(1, "hello", true); // type: [string, boolean]
๐๏ธ Conditional Types with Spread
Combine spread with advanced type manipulation:
// ๐งฌ Deep partial type using spread
type DeepPartial<T> = T extends object ? {
[P in keyof T]?: DeepPartial<T[P]>;
} : T;
// ๐จ Merge function with deep type safety
function deepMerge<T extends object, U extends DeepPartial<T>>(
target: T,
...sources: U[]
): T & U {
const result = { ...target };
sources.forEach(source => {
Object.keys(source).forEach(key => {
const sourceValue = source[key as keyof U];
const targetValue = result[key as keyof T];
if (typeof sourceValue === 'object' && typeof targetValue === 'object') {
(result as any)[key] = deepMerge(targetValue as any, sourceValue as any);
} else {
(result as any)[key] = sourceValue;
}
});
});
return result as T & U;
}
// ๐ Usage
interface AppConfig {
theme: {
colors: {
primary: string;
secondary: string;
};
fonts: string[];
};
features: {
analytics: boolean;
notifications: boolean;
};
}
const baseConfig: AppConfig = {
theme: {
colors: { primary: "blue", secondary: "gray" },
fonts: ["Arial", "Helvetica"]
},
features: {
analytics: true,
notifications: false
}
};
const merged = deepMerge(baseConfig,
{ theme: { colors: { primary: "red" } } },
{ features: { notifications: true } }
);
๐จ Variadic Tuple Types
// ๐ฏ Advanced tuple manipulation
type Concat<T extends readonly unknown[], U extends readonly unknown[]> = [...T, ...U];
type Zip<T extends readonly unknown[], U extends readonly unknown[]> = {
[K in keyof T]: K extends keyof U ? [T[K], U[K]] : never;
};
// ๐บ๏ธ Utility functions using variadic tuples
function concat<T extends readonly unknown[], U extends readonly unknown[]>(
arr1: T,
arr2: U
): Concat<T, U> {
return [...arr1, ...arr2] as Concat<T, U>;
}
function zip<T extends readonly unknown[], U extends readonly unknown[]>(
arr1: T,
arr2: U
): Zip<T, U> {
return arr1.map((item, index) =>
[item, arr2[index]]
) as Zip<T, U>;
}
// Usage with type inference
const combined = concat([1, 2] as const, ["a", "b"] as const);
// Type: readonly [1, 2, "a", "b"]
const paired = zip(["x", "y", "z"] as const, [1, 2, 3] as const);
// Type: readonly [["x", 1], ["y", 2], ["z", 3]]
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Rest Parameter Position
// โ Wrong - rest parameter must be last!
function badFunction(...items: string[], lastItem: number): void {
// TypeScript Error: A rest parameter must be last in a parameter list
}
// โ
Correct - rest parameter at the end
function goodFunction(firstItem: number, ...items: string[]): void {
console.log(`First: ${firstItem}, Rest: ${items.join(", ")}`);
}
// ๐ฏ Remember: Only ONE rest parameter allowed
function process(category: string, ...items: string[]): void {
// Perfect! Category first, then collect all items
}
๐คฏ Pitfall 2: Mutation vs Immutability
// โ Dangerous - mutating original array
const original = [1, 2, 3];
const extended = original; // This is NOT a copy!
extended.push(4);
console.log(original); // [1, 2, 3, 4] ๐ฑ Original changed!
// โ
Safe - using spread for immutability
const original2 = [1, 2, 3];
const extended2 = [...original2, 4]; // Creates new array
console.log(original2); // [1, 2, 3] โ
Original unchanged!
// ๐จ Same with objects
const user = { name: "Alice", age: 30 };
// โ Wrong
const updated = user;
updated.age = 31; // Mutates original!
// โ
Correct
const updated2 = { ...user, age: 31 }; // New object
๐ต Pitfall 3: Spread Order Matters
// โ ๏ธ Order affects final values
const defaults = { theme: "light", fontSize: 14, debug: false };
const userPrefs = { theme: "dark", debug: true };
// Different results based on order!
const config1 = { ...defaults, ...userPrefs };
// Result: { theme: "dark", fontSize: 14, debug: true }
const config2 = { ...userPrefs, ...defaults };
// Result: { theme: "light", fontSize: 14, debug: false } ๐ฑ
// โ
Be explicit about precedence
const finalConfig = {
...defaults, // Base configuration
...userPrefs, // User overrides
version: "1.0.0" // Additional properties
};
๐ค Pitfall 4: Type Loss with Spread
// โ ๏ธ TypeScript may lose type information
const numbers = [1, 2, 3] as const;
const moreNumbers = [...numbers, 4]; // Type: number[], not [1, 2, 3, 4]
// โ
Preserve types when needed
const tuple = [1, "hello", true] as const;
const preservedTuple = [...tuple] as const; // Keeps readonly tuple type
// ๐จ Function overloads for type preservation
function combine<T extends readonly unknown[], U extends readonly unknown[]>(
arr1: T,
arr2: U
): [...T, ...U] {
return [...arr1, ...arr2];
}
const combined = combine([1, 2] as const, ["a", "b"] as const);
// Type: [1, 2, "a", "b"]
๐ฌ Pitfall 5: Deep vs Shallow Copy
// โ Spread only does shallow copy!
const nested = {
user: { name: "Alice", settings: { theme: "dark" } },
scores: [100, 200]
};
const copied = { ...nested };
copied.user.settings.theme = "light";
console.log(nested.user.settings.theme); // "light" ๐ฑ Original changed!
// โ
Deep copy solution
function deepCopy<T>(obj: T): T {
if (obj === null || typeof obj !== "object") return obj;
if (obj instanceof Date) return new Date(obj.getTime()) as any;
if (Array.isArray(obj)) return obj.map(item => deepCopy(item)) as any;
const cloned = {} as T;
for (const key in obj) {
cloned[key] = deepCopy(obj[key]);
}
return cloned;
}
const deepCopied = deepCopy(nested);
deepCopied.user.settings.theme = "light";
console.log(nested.user.settings.theme); // "dark" โ
Original unchanged!
๐ ๏ธ Best Practices
1. ๐ฏ Use Rest Parameters for Flexibility
// โ
Good - flexible API
function log(level: string, ...messages: unknown[]): void {
console.log(`[${level}]`, ...messages);
}
// Usage is intuitive
log("INFO", "Server started");
log("ERROR", "Failed to connect", { code: 500 }, new Error());
2. ๐ Preserve Immutability with Spread
// โ
Good - always create new objects/arrays
const addItem = <T>(array: readonly T[], item: T): T[] => {
return [...array, item]; // New array
};
const updateUser = (user: User, updates: Partial<User>): User => {
return { ...user, ...updates }; // New object
};
3. ๐ก๏ธ Type Rest Parameters Properly
// โ Avoid any[]
function bad(...args: any[]): void { }
// โ
Be specific
function good(...args: string[]): void { }
function better(...args: Array<string | number>): void { }
function best<T>(...args: T[]): void { }
4. ๐จ Use Const Assertions for Tuples
// โ
Preserve literal types
const point = [10, 20] as const; // readonly [10, 20]
const config = { host: "localhost", port: 3000 } as const;
// Spread preserves the types
const newPoint = [...point, 30] as const; // readonly [10, 20, 30]
5. โจ Combine Patterns Effectively
// โ
Rest + spread + defaults = powerful APIs
class Logger {
constructor(
private defaultTags: string[] = []
) {}
log(message: string, ...additionalTags: string[]): void {
const allTags = [...this.defaultTags, ...additionalTags];
console.log(`[${allTags.join(", ")}] ${message}`);
}
}
const logger = new Logger(["app", "v1"]);
logger.log("Started", "startup", "info");
// Output: [app, v1, startup, info] Started
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Flexible Command System
Create a type-safe command system using rest parameters and spread operator:
๐ Requirements:
- โ Commands with variable arguments
- ๐ท๏ธ Command chaining and composition
- ๐ค Middleware support
- ๐ Command history with undo
- ๐จ Type-safe command parsing
๐ Bonus Points:
- Add command aliases
- Implement command piping
- Create batch execution
๐ก Solution
๐ Click to see solution
// ๐ฏ Flexible command system with rest & spread
type CommandHandler = (...args: string[]) => void | Promise<void>;
type Middleware = (args: string[], next: () => void) => void;
interface Command {
name: string;
aliases: string[];
handler: CommandHandler;
description: string;
}
class CommandSystem {
private commands = new Map<string, Command>();
private history: Array<{ command: string; args: string[] }> = [];
private middlewares: Middleware[] = [];
// ๐ Register commands with aliases
register(name: string, handler: CommandHandler, ...aliases: string[]): this {
const command: Command = {
name,
aliases,
handler,
description: ""
};
// Register main command and all aliases
this.commands.set(name, command);
aliases.forEach(alias => this.commands.set(alias, command));
console.log(`โจ Registered command: ${name} ${aliases.length > 0 ? `(aliases: ${aliases.join(", ")})` : ""}`);
return this;
}
// ๐จ Add middleware
use(...middlewares: Middleware[]): this {
this.middlewares.push(...middlewares);
return this;
}
// ๐ Execute command with arguments
async execute(input: string): Promise<void> {
const [commandName, ...args] = input.split(" ");
const command = this.commands.get(commandName);
if (!command) {
console.log(`โ Unknown command: ${commandName}`);
return;
}
// Save to history
this.history.push({ command: commandName, args });
// Run through middlewares
let index = 0;
const next = (): void => {
if (index < this.middlewares.length) {
const middleware = this.middlewares[index++];
middleware(args, next);
} else {
// Execute the actual command
console.log(`๐ฎ Executing: ${commandName} ${args.join(" ")}`);
command.handler(...args);
}
};
next();
}
// ๐ Batch execute multiple commands
async batch(...commands: string[]): Promise<void> {
console.log(`๐จ Batch executing ${commands.length} commands`);
for (const cmd of commands) {
await this.execute(cmd);
}
}
// ๐๏ธ Create command pipeline
pipe(...commandStrings: string[]): (...initialArgs: string[]) => void {
return (...initialArgs: string[]) => {
let result = initialArgs;
commandStrings.forEach(cmdStr => {
const [cmdName, ...cmdArgs] = cmdStr.split(" ");
const command = this.commands.get(cmdName);
if (command) {
// Pass previous result as additional arguments
command.handler(...cmdArgs, ...result);
}
});
};
}
// ๐ Show command history
showHistory(limit?: number): void {
const historyToShow = limit ? this.history.slice(-limit) : this.history;
console.log("\n๐ Command History:");
historyToShow.forEach((entry, index) => {
console.log(` ${index + 1}. ${entry.command} ${entry.args.join(" ")}`);
});
}
// ๐ Undo last command (simplified)
undo(): void {
const last = this.history.pop();
if (last) {
console.log(`โฉ๏ธ Undoing: ${last.command} ${last.args.join(" ")}`);
}
}
}
// ๐ฎ Usage Example
const cli = new CommandSystem();
// Register commands with rest parameters
cli.register("echo", (...args) => {
console.log(`๐ข ${args.join(" ")}`);
}, "print", "say");
cli.register("add", (...numbers) => {
const sum = numbers.reduce((acc, n) => acc + parseFloat(n), 0);
console.log(`โ Sum: ${sum}`);
});
cli.register("concat", (...strings) => {
console.log(`๐ ${strings.join("")}`)
});
cli.register("list", (...items) => {
console.log("๐ List:");
items.forEach((item, i) => console.log(` ${i + 1}. ${item}`));
});
// Add middleware
cli.use(
(args, next) => {
console.log(`๐ Middleware: Processing ${args.length} arguments`);
next();
},
(args, next) => {
if (args.some(arg => arg.includes("error"))) {
console.log(`โ ๏ธ Warning: Arguments contain 'error'`);
}
next();
}
);
// Execute commands
await cli.execute("echo Hello TypeScript World");
await cli.execute("add 10 20 30 40");
await cli.execute("list apple banana cherry date");
// Batch execution
await cli.batch(
"echo Starting batch",
"add 1 2 3",
"concat Type Script Rocks",
"echo Batch complete"
);
// Create pipeline
const processData = cli.pipe("echo", "concat");
processData("Hello", "World");
// Show history
cli.showHistory(5);
// Test aliases
await cli.execute("say This uses an alias!");
await cli.execute("print Another alias test");
๐ Key Takeaways
Youโve mastered rest parameters and spread operator! Hereโs what you can now do:
- โ Handle variable arguments with type-safe rest parameters ๐ช
- โ Clone and merge arrays/objects without mutations ๐ก๏ธ
- โ Compose functions with flexible parameter handling ๐ฏ
- โ Build flexible APIs that users love ๐
- โ Write cleaner code with modern JavaScript patterns! ๐
Remember: Rest collects, spread expands! These tools make your code more flexible and maintainable. ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered rest parameters and spread operator!
Hereโs what to do next:
- ๐ป Complete the command system exercise above
- ๐๏ธ Refactor existing code to use spread for immutability
- ๐ Move on to our next tutorial: Function Overloading
- ๐ Create flexible APIs in your projects!
Remember: These patterns are everywhere in modern TypeScript. The more you use them, the more natural they become! ๐
Happy coding! ๐๐โจ