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! ❤️