Typescript has a concept called ‘Declaration Merging’. The idea is that some kinds of declarations can be specified multiple times, and the ‘final’ or ‘actual’ value is the result of those combined declarations:
interface A { name: string }
interface A { age: number }
Here, we declare using declaration merging to define an interface that is the same as:
interface A { name: string; age: number }
This is really useful for writing code along with contextual, but global type information. Consider this example of defining Action types in the common reducer
pattern of event handling / dispatch:
enum ActionKind { }enum ActionKind { AddFile = 0 }
interface ActionAddFile {
type: ActionKind.AddFile
file: File
}enum ActionKind { DeleteFile = 1 }
interface ActionDeleteFile {
type: ActionKind.DeleteFile
file: File
}
We’ve nicely defined two action types: one, AddFile
adds a file, while the other DeleteFile
deletes a file. These can be uniquely identified by their ActionKind
.
Writing the actions this way avoids having to have a big enum ActionKind{ AddFile, DeleteFile, Something, SomethingElse }
which would have to be maintained separately to the actions themselves.
Almost always, however, we also want an Action
type that contains a union, to be discriminated on Action.type
, i.e:
type Action = ActionAddFile | ActionDeleteFile
This tends to become unwieldy after a while, as every time you add an Action interface, you must also add it to the union, and that’s easy to forget.
You can’t merge type aliases (what this construct is called) the same way you can interfaces or enum
s, attempting to do so will make typescript complain that you’ve declared the same alias identifier twice:
type Action = ActionAddFile
type Action = ActionDeleteFile
And looking at it, well — it doesn’t look right anyway. Since aliases are not ‘true’ types in that the type system considers them to be equivalent to their right hand side, there’s not really anything to sanely merge.
However! I’ve started to use a pattern that uses interface merging to achieve the same thing.
In this form, we define an interface _Action
whose members will be merged to get all the actions as we…