Declaration merging in TypeScript is a powerful feature that allows multiple declarations of the same name to be merged into a single declaration. This capability is particularly useful when working with complex types, extending existing libraries, and creating flexible APIs. This chapter will provide a comprehensive understanding of declaration merging in TypeScript, starting from basic concepts to advanced use cases, with detailed explanations, examples, and code outputs.
Declaration merging occurs when the TypeScript compiler merges multiple declarations of the same name into a single entity. This can happen with interfaces, functions, classes, enums, and namespaces. The merged declaration combines the properties and behaviors of all individual declarations.
Let’s start with a basic example involving interfaces.
// First declaration
interface User {
name: string;
}
// Second declaration
interface User {
age: number;
}
// Merged interface
const user: User = {
name: 'Alice',
age: 30
};
console.log(user);
User interface.name and age properties.user that conforms to the merged User interface.
{ name: 'Alice', age: 30 }
When interfaces with the same name are declared, their properties are merged.
// First declaration
interface Point {
x: number;
}
// Second declaration
interface Point {
y: number;
}
// Merged interface
const point: Point = {
x: 10,
y: 20
};
console.log(point);
Point interfaces are declared with different properties.x and y properties.point that has both properties.
{ x: 10, y: 20 }
Interfaces can extend other interfaces, and merged declarations can also include extensions.
// Base interface
interface Shape {
color: string;
}
// First declaration
interface Circle extends Shape {
radius: number;
}
// Second declaration
interface Circle {
circumference: number;
}
// Merged interface
const circle: Circle = {
color: 'red',
radius: 10,
circumference: 62.8
};
console.log(circle);
Circle interface extends Shape and is declared twice with additional properties.Circle declarations and includes the properties from Shape.circle that has color, radius, and circumference properties.
{ color: 'red', radius: 10, circumference: 62.8 }
Functions can also be merged. When two functions with the same name are declared, their overloads are combined.
// First declaration
function add(a: number, b: number): number;
// Second declaration
function add(a: string, b: string): string;
// Implementation
function add(a: any, b: any): any {
return a + b;
}
console.log(add(1, 2)); // Output: 3
console.log(add('Hello, ', 'World!')); // Output: Hello, World!
add function declarations with different parameter types.
3
Hello, World!
Namespaces allow grouping of related code. Declaration merging with namespaces combines their contents.
// First declaration
namespace Animals {
export class Cat {
meow() {
console.log('Meow');
}
}
}
// Second declaration
namespace Animals {
export class Dog {
bark() {
console.log('Woof');
}
}
}
// Using merged namespace
const cat = new Animals.Cat();
const dog = new Animals.Dog();
cat.meow(); // Output: Meow
dog.bark(); // Output: Woof
Animals namespaces are declared with different classes.Animals namespace containing both Cat and Dog classes.Cat and Dog and call their methods.
Meow
Woof
Classes can also participate in declaration merging. However, this is less common and more complex.
// First declaration
class Vehicle {
wheels: number = 4;
}
// Second declaration
interface Vehicle {
engine: string;
}
// Merged class and interface
const car: Vehicle = {
wheels: 4,
engine: 'V8'
};
console.log(car);
Explanation:
Vehicle class is declared with a wheels property.Vehicle is declared with an engine property.
{ wheels: 4, engine: 'V8' }
Enums can be merged to combine their members.
// First declaration
enum Status {
Active,
Inactive
}
// Second declaration
enum Status {
Pending,
Completed
}
// Using merged enum
console.log(Status.Active); // Output: 0
console.log(Status.Pending); // Output: 2
console.log(Status.Completed); // Output: 3
Status enum is declared twice with different members.
0
2
3
Declaration merging is useful for extending existing libraries with additional functionality.
Consider an existing library library with a namespace Library.
// library.d.ts
declare namespace Library {
function existingFunction(): void;
}
// Extending the library
declare namespace Library {
function newFunction(): void;
}
// Using extended library
Library.existingFunction();
Library.newFunction();
library.d.ts provides the original declaration.Library namespace with a new function.Declaration merging allows adding properties to global objects.
// Existing global object
declare global {
interface Window {
customProperty: string;
}
}
// Adding a property
window.customProperty = 'Hello, World!';
console.log(window.customProperty);
Window interface.customProperty is added to window.
Hello, World!
TypeScript's declaration merging is a versatile feature that allows combining multiple declarations of the same name into a single entity. This chapter covered the basics of declaration merging with interfaces, functions, namespaces, classes, and enums, along with advanced use cases and practical applications. Happy coding! ❤️
