2021-12-22
|~3 min read
|411 words
Declaration merging is a powerful concept in Typescript that allows you to extend type definitions in many ways.
From the Typescript documentation on Declaration Merging
“declaration merging” means that the compiler merges two separate declarations declared with the same name into a single definition. This merged definition has the features of both of the original declarations.
The simplest / most common form of declaration is when you merge two interfaces:
interface Bar {
baz?: string
}
interface Bar {
wam?: number
}
const bar: Bar = {
baz: "abc",
wam: 1,
cat: 123, // Error
}
In the above, the two Bar
interfaces are merged and bar
can readily accept baz
and/or wam
, though it complains at cat
which is not part of the interface.
Perhaps more interesting is module augmentation.
As the name implies, module augmentation is when you accept an interface exported by a module, whether that’s locally defined or imported from a third party library, and augment it with your own details.
A common use case for this is when dealing with an API server and you need to extend the general Request
/ Response
types because of middleware (example here).
Using the example from the docs, imagine you have a module observable.ts
:
observable.tsexport class Observable<T> { // ... implementation left as an exercise for the reader ... }
Then, in a separate module, map.ts
, you want to make use of the Observable
, but extend the class with a new prototype method:
map.tsimport { Observable } from "./observable" Observable.prototype.map = function (f) { // ... another exercise for the reader }
This works in Javascript, but Typescript will complain as the interface Observable
doesn’t have a map
method on it.
Using module augmentation, we can fix that:
map.tsimport { Observable } from "./observable"; + declare module "./observable" { + interface Observable<T> { + map<U>(f: (x: T) => U): Observable<U>; + } + } Observable.prototype.map = function (f) { // ... another exercise for the reader };
Now, when we want to use it, Typescript knows that the method is available, for example in a consumer.ts
module:
consumer.tsimport { Observable } from "./observable" import "./map" let o: Observable<number> o.map((x) => x.toFixed())
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!